【问题标题】:Python descriptor for type checks and immutability用于类型检查和不变性的 Python 描述符
【发布时间】:2018-08-29 17:50:04
【问题描述】:

阅读 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 是“全局”的,因为它在类的所有实例中都是单例。当 t2 被实例化时,is_set 已经从前一个对象为 True。

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

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

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

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

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

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

这似乎是错误的。

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

【问题讨论】:

  • 您是否尝试过使用一个只有__get__ 方法且没有设置不变性的非继承类。另外,如果值是不可变的,为什么需要强制进行类型检查?这不是自相矛盾吗?
  • 目的是将设置限制在(内部)__init__ 方法(就像 Java 构造函数可以设置最终变量一样),同时还检查类型。实际上,可以实现方法来进行类型检查,同时通过@property 保持不变性。不过,同时做这两件事似乎更优雅。

标签: python immutability typechecking descriptor


【解决方案1】:

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

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__ 方法中键入了该属性。

【讨论】:

  • 目前无法访问 Python 解释器,如果有问题或无法解决您的问题,请发表评论:)
  • __set_name__ 是 Python 3.6+。尽管如此,由于与我的尝试 #1 相同的原因,该代码无法正常工作。
【解决方案2】:

无法成功制作这样的描述符。也许它也不必要地复杂。以下方法+property使用即可。

# this also allows None to go through
def check_type(data, expected_types):
    if data is not None and not isinstance(data, expected_types):
        raise TypeError('Expected: {}'.format(str(expected_types)))

    return data

class A():
    def __init__(self, value=None):
        self._value = check_type(value, (str, bytes))

    @property
    def value(self):
        return self._value


foo = A()
print(foo.value)    # None
foo.value = 'bla'   # AttributeError
bar = A('goosfraba')
print(bar.value)    # goosfraba
bar.value = 'bla'   # AttributeError

【讨论】:

    【解决方案3】:
    class ImmutableTyped(object):
    
        def __set_name__(self, owner, name):
            self.name = name
    
        def __init__(self, *, types=None):
    
            self.types = tuple(types or [])
    
            self.instances = {}
    
            return None
    
        def __get__(self, instance, owner):
            return instance.__dict__[self.name]
    
        def __set__(self, instance, value):
    
            is_set = self.instances.setdefault(id(instance), False)
    
            if is_set:
                raise AttributeError("read-only attribute '%s'" % (self.name))
    
            if self.types:
    
                if not isinstance(value, self.types):
                    raise TypeError("invalid argument type '%s' for '%s'" % (type(value), self.name))
    
            self.instances[id(instance)] = True
    
            instance.__dict__[self.name] = value
    
            return None
    

    示例:

    class Something(object):
    
        prop1 = ImmutableTyped(types=[int])
    
    something = Something()
    something.prop1 = "1"
    

    将给予:

    TypeError: invalid argument type '<class 'str'>' for 'prop1'
    

    还有:

    something = Something()
    something.prop1 = 1
    something.prop1 = 2
    

    将给予:

    TypeError: read-only attribute 'prop1'
    

    【讨论】:

      猜你喜欢
      • 2021-10-11
      • 1970-01-01
      • 2018-01-12
      • 2018-05-12
      • 1970-01-01
      • 2012-05-19
      • 1970-01-01
      • 1970-01-01
      • 2016-08-03
      相关资源
      最近更新 更多