我想防止实例的用户在使用所述对象时错误地创建一个不存在的属性。
假设我有一个带有初始化和一些属性的类:
class Foo(obj):
def __init__(self, a, b):
self.a = a
self.b = b
我想在阻止创建新属性的同时启用设置现有属性:
myobj = foo(1,2)
>>
print(myobj.a)
>> 1
myobj.a = 2
myobj.c = 1
>> AttributeError: frozen instance
使用
__setattr__
覆盖和布尔值相当容易:
class Foo(obj):
_is_frozen = False
def __init__(self, a, b):
self.a = a
self.b = b
self._is_frozen = True
def __setattr__(self, name, value):
if not self._is_frozen or hasattr(self, name):
super().__setattr__(name, value)
else:
raise AttributeError("frozen instance")
现在我挣扎的步骤是当一个新类继承 Foo 时。如果在调用
super().__init__()
之后必须定义新属性,则实例将被冻结。
我曾尝试使用装饰器制作元类,但母类的装饰器仍在
super().__init__()
调用中被调用,我无法定义我的新属性。
换句话说,有没有办法制作一种
__post_init__
方法(对数据类模块的引用),只有在所有初始化(类中的一个和继承类中的一个)被调用后才会被调用
确实-这很棘手。
在基类的
__init__
上有一个装饰器,它会在__init__
之后冻结实例,正如你提到的有状态问题 - 可以添加其他状态变量,或计算__init__
深度,并使用一个元类(或__init_subclass__
)来装饰子类中的所有__init__
方法,这样它只会在退出最外面的__init__
.时进行冻结
但是有一种使用元类的更简单的方法:元类的
__call__
方法是调用类__new__
然后在创建新实例时调用__init__
。因此,只需将代码用于在自定义元类上调用__post_init__
__call__
(或直接从中冻结实例)
class PostInitMeta(type):
def __call__(cls, *args, **kw):
instance = super().__call__(*args, **kw) # < runs __new__ and __init__
instance.__post_init__()
return instance
class Freezing(metaclass=PostInitMeta):
_frozen = False
def __post_init__(self):
self._frozen = True
def __setattr__(self, name, value):
if self._frozen:
raise AttributeError() # or do nothing, and return as you prefer
super().__setattr__(name, value)
class A(Freezing):
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
并在交互模式下测试:
In [18]: b = B(2, 3)
In [19]: b.a
Out[19]: 2
In [20]: b.b
Out[20]: 3
In [21]: b.b = 5
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In [21], line 1
----> 1 b.b = 5
使用
__slots__
有一个更简单的解决方案。
注意:有一些用例可能禁止使用
__slots__
,检查Using Slots。
class Foo:
__slots__ = ['a', 'b']
def __init__(self, a, b):
self.a = a
self.b = b
foo = Foo(1,2)
foo.c = 3 # => attribute error - 'Foo' object has no attribute 'c'
class Bar(Foo):
__slots__ = ['c']
def __init__(self, a, b, c):
super().__init__(a, b)
self.c = 3
bar = Bar(1,2,3)
bar.d = 4 # => attribute error - 'Bar' object has no attribute 'd'