首页 > 解决方案 > 用于类型检查和不变性的 Python 描述符

问题描述

阅读 Python Cookbook 并查看描述符,尤其是使用类属性时强制类型的示例。我正在编写一些有用的类,但我也想强制执行不变性。怎么做?改编自本书的类型检查描述符:

class Descriptor(object):
    def __init__(self, name=None, **kwargs):
        self.name = name

        for key, value in kwargs.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        instance.__dict__[self.name] = value


# by default allows None
class Typed(Descriptor):
    def __init__(self, expected_types=None, **kwargs):
        self.expected_types = expected_types

        super().__init__(**kwargs)

    def __set__(self, instance, value):
        if value is not None and not isinstance(value, self.expected_types):
            raise TypeError('Expected: {}'.format(str(self.expected_types)))

        super(Typed, self).__set__(instance, value)

class T(object):
    v = Typed(int)

    def __init__(self, v):
        self.v = v

尝试 #1:self.is_set向 Typed 添加一个属性

# by default allows None
class ImmutableTyped(Descriptor):
    def __init__(self, expected_types=None, **kwargs):
        self.expected_types = expected_types
        self.is_set = False

        super().__init__(**kwargs)

    def __set__(self, instance, value):
        if self.is_set:
            raise ImmutableException(...)
        if value is not None and not isinstance(value, self.expected_types):
            raise TypeError('Expected: {}'.format(str(self.expected_types)))

        self.is_set = True

        super(Typed, self).__set__(instance, value)

错误的,因为在执行以下操作时,ImmutableTyped它是“全局”的,因为它在类的所有实例中都是一个单例。Whent2被实例化,is_set从前一个对象中已经是 True 。

class T(object):
    v = ImmutableTyped(int)

    def __init__(self, v):
        self.v = v

t1 = T()
t2 = T()  # fail when instantiating

尝试#2:Thought instance in__set__是指包含该属性的类,因此尝试检查是否instance.__dict__[self.name]仍然是 Typed。那也是错误的。

@property想法#3:通过接受fget返回__dict__T 个实例的 ' ' 方法,使 Typed 的使用更类似于。这需要在 T 中定义一个函数,类似于:

@Typed
def v(self):
    return self.__dict__

这似乎是错误的。

如何实现不变性和类型检查作为描述符?

标签: pythonimmutabilitytypecheckingdescriptor

解决方案


现在这是我解决问题的方法:

class ImmutableTyped:
    def __set_name__(self, owner, name):
        self.name = name

    def __init__(self, *, immutable=False, types=None)
        self.immutable == immutable is True
        self.types = types if types else []

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if self.immutable is True:
            raise TypeError('read-only attribute')
        elif not any(isinstance(value, cls)
                     for cls in self.types):
            raise TypeError('invalid argument type')
        else:
           instance.__dict__[self.name] = value

旁注:__set_name__可用于允许您在初始化时不指定属性名称。这意味着您可以这样做:

class Foo:
    bar = ImmutableTyped()

并且实例ImmutableTyped将自动具有该name属性bar,因为我在__set_name__方法中键入了该属性。


推荐阅读