什么情况下我应该使用 attr.ib(default=attr.Factory(list))
而不是 attr.ib(default=[])
?
从文档中,我看到 Factory 用于生成新的值,如果你正在使用带有输入的 lambda 表达式,那么这很有意义;但是,如果你只是生成一个空列表,我不明白为什么要这样做。
我在这里错过了什么吗?
什么情况下我应该使用 attr.ib(default=attr.Factory(list))
而不是 attr.ib(default=[])
?
从文档中,我看到 Factory 用于生成新的值,如果你正在使用带有输入的 lambda 表达式,那么这很有意义;但是,如果你只是生成一个空列表,我不明白为什么要这样做。
我在这里错过了什么吗?
您希望避免使用可变对象作为默认值。如果您使用attr.ib(default=[])
,则生成的是一个__init__
方法,其中将该列表对象用作关键字参数默认值:
def __init__(self, foo=[]):
self.foo = foo
参数的默认值是在定义时创建的,仅创建一次。每次调用方法时不会重新评估它们。对该对象的任何更改都将在所有实例之间共享。请参见"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'], [])
由于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))
< p > 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()
inspect.getargspec
,替代方案似乎是inspect.getfullargspec
或inspect.signature
。 - undefined
default=[]
生成一个空列表。一,大家都要分享。 - user2357112