何时以及为什么要使用attr.Factory?

时间:2017-05-23 17:11:06

标签: python python-3.x python-attrs

何时以及为什么我应该attr.ib(default=attr.Factory(list))使用attr.ib(default=[])

docs我看到Factory用于生成一个新值,如果你使用带输入的lambda表达式,这是有意义的;但是,如果您只是生成一个空列表,我不明白为什么要使用它。

我在这里缺少什么?

1 个答案:

答案 0 :(得分:10)

您希望避免使用可变对象作为默认值。如果您使用attr.ib(default=[]),那么生成的是使用该列表对象作为关键字参数默认值的__init__方法:

def __init__(self, foo=[]):
    self.foo = foo

参数的默认值在定义时创建一次。每次调用方法时都不会重新评估它们。然后,对所有实例共享共享该对象的任何突变。请参阅"Least Astonishment" and the Mutable Default Argument

然而,使用attr.Factory()方法,默认设置为sentinel,当参数保留为s​​entinel值时,函数本身中的 是值,然后替换为打电话给工厂的结果。这相当于:

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))

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()