用于类型检查和不变性的Python描述符

问题描述 投票:0回答:2

阅读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:向Typed添加self.is_set属性

# 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__中的思想实例引用包含该属性的类,因此试图检查instance.__dict__[self.name]是否仍然是Typed。那也是错的。

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

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

这似乎不对。

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

python immutability typechecking descriptor
2个回答
0
投票

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

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__方法。


0
投票

无法成功制作这样的描述符。也许它也不必要地复杂化。以下方法+ 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
© www.soinside.com 2019 - 2024. All rights reserved.