Когда и почему я должен использовать attr.Factory?

Когда и почему я должен использовать attr.ib(default=attr.Factory(list)) над attr.ib(default=[])?

Из документации я вижу, что Factory используется для генерации нового значения, что имеет смысл, если вы используете лямбда-выражение с входными данными; однако я не понимаю, почему вы бы использовали его, если вы просто генерируете пустой список.

Что мне здесь не хватает?

1 ответ

Решение

Вы хотите избежать использования изменяемых объектов в качестве значений по умолчанию. Если вы использовали attr.ib(default=[]), что генерируется __init__ метод с использованием этого объекта списка в качестве ключевого аргумента default:

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

Значения по умолчанию для аргументов создаются один раз, во время определения. Они не переоцениваются каждый раз, когда вы вызываете метод. Любые мутации этого объекта затем передаются всем экземплярам. См. "Наименьшее удивление" и Аргумент изменяемого по умолчанию.

С использованием 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))

Значение по умолчанию для 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()
Другие вопросы по тегам