__setattr__ 方法确保类中的对象不可变
我定义了一个类(这里面有很多内容被简化了),但我主要想问的是,一旦这个类的对象被创建并且它的变量被初始化后,我希望这些变量在类外部是不能被改变的。如果有人尝试去改变这些变量,我就需要抛出一个AssertionError(断言错误)。我已经定义了setattr方法来抛出这个异常,但在加入这个方法后,之前我对这个类进行的实例和测试都被完全破坏了,每当尝试创建这个类的对象时,都会抛出同样的AssertionError。因此,这里是我类的一个大幅简化版本。
class Interval:
compare_mode = None
def __init__(self, min, max):
self.min = min
self.max = max
def min_max(min, max = None)-> 'Interval Object':
.
.
.
return Interval(min, max)
其他类的方法...
def __setattr__(self, name, value):
raise AssertionError('Objects in this class are immutable')
所以当我尝试测试这个类的对象是否可以被修改时,我得到了错误的异常,同时也破坏了我在这个类中其他方法的测试。
p.min = 0; raised wrong exception(NameError)
p.max = 0; raised wrong exception(NameError)
p.HeyImChangingYourMethodandThereIsNothingYouCanDoToStopME raised wrong exception(NameError)
2 个回答
你的 __setattr__
方法会阻止对象自己设置属性,比如在 __init__
方法中设置的 self.min
和 self.max
。
如果你想避免这种情况,可以在 __init__
方法的最后设置一个标志,告诉程序这个对象是否已经初始化,然后让 __setattr__
方法检查这个标志,如果标志已经设置,就不允许再设置属性。或者,你可以在 __init__
方法中直接修改 self.__dict__
来设置属性(例如,self.__dict__['min'] = min
)。
如果你只需要初始化几个这样的属性,使用 property
来定义只读属性可能会更清晰。不过,和使用 __setattr__
不同,这样做并不会阻止用户给你的类添加新的属性。
你的问题有点难懂,不过在 __init__
里,你可以做类似下面的事情:
self.max = 0
这会调用 __setattr__
,并立即抛出你的异常。
如果你真的想要不可变性,可以考虑使用 collections.namedtuple
加上 __slots__
:
class Interval(collections.namedtuple('IntervalBase', 'min,max')):
__slots__ = ()
def method1(self):
print self.min
print self.max
下面是一个示例:
>>> class Interval(collections.namedtuple('IntervalBase', 'min,max')):
... __slots__ = ()
... def method1(self):
... print self.min
... print self.max
...
>>> Interval(1,2)
IntervalBase(min=1, max=2)
>>> Interval(1,2).method1
<bound method Interval.method1 of IntervalBase(min=1, max=2)>
>>> Interval(1,2).method1()
1
2
>>> Interval(1,2).foo = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Interval' object has no attribute 'foo'
需要注意的是,用户仍然可以在 Interval
的子类中添加属性,除非那个子类也使用了 __slots__
……
另外,通常你不需要担心用户在你的实例上添加临时属性。如果没有任何提供的 API 方法可以改变(你可以通过 property
来强制执行),那么在所有实际意义上,你的类就是 不可变 的,这样你就可以享受不可变性带来的好处(比如合理定义的 __hash__
)。