阅读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__
这似乎不对。
如何将不变性和类型检查作为描述符实现?
现在这是我解决问题的方法:
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__
方法。
无法成功制作这样的描述符。也许它也不必要地复杂化。以下方法+ 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