你想要avoid使用可变对象作为默认值。如果你用过attr.ib(default=[])
,生成的是__init__
使用该列表对象作为默认关键字参数的方法:
def __init__(self, foo=[]):
self.foo = foo
创建参数的默认值once,在定义时。每次调用该方法时都不会重新评估它们。然后对该对象的任何突变在所有实例之间共享. See “最少惊讶”和可变默认参数 https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument.
使用attr.Factory()
然而,方法默认设置为哨兵,并且当参数保留为哨兵值时,在函数本身是随后替换为调用工厂的结果的值。这相当于:
def __init__(self, foo=None):
if foo is None:
foo = []
self.foo = foo
现在,每个实例都会创建一个新的列表对象。
一个演示差异的快速演示:
>>> import attr
>>> @attr.s
... class Demo:
... foo = attr.ib(default=[])
... bar = attr.ib(default=attr.Factory(list))
...
>>> d1 = Demo()
>>> d1.foo, d1.bar
([], [])
>>> d1.foo.append('d1'), d1.bar.append('d1')
(None, None)
>>> d1.foo, d1.bar
(['d1'], ['d1'])
>>> d2 = Demo()
>>> d2.foo, d2.bar
(['d1'], [])
Because demo.foo
正在使用共享列表对象,通过以下方式对其进行更改d1.foo
在任何其他情况下立即可见。
当我们使用inspect.getargspec()
看看Demo.__init__
方法,我们看看为什么:
>>> import inspect
>>> inspect.getargspec(Demo.__init__)
ArgSpec(args=['self', 'foo', 'bar'], varargs=None, keywords=None, defaults=(['d1'], NOTHING))
默认值为foo
是完全相同的列表对象,附加了'd1'
字符串还在那里。bar
被设置为哨兵对象(这里使用attr.NOTHING
;一个可以使用的值Demo(bar=None)
没有变成列表对象):
>>> print(inspect.getsource(Demo.__init__))
def __init__(self, foo=attr_dict['foo'].default, bar=NOTHING):
self.foo = foo
if bar is not NOTHING:
self.bar = bar
else:
self.bar = __attr_factory_bar()