Функция Python, «запоминающая» предыдущий аргумент (** kwargs) - PullRequest
4 голосов
/ 22 июля 2011

У меня есть несколько объектов, у которых есть словарь атрибутов, obj.attrs.Конструктор для этих объектов для удобства принимает dict и / или **kwargs.

Это выглядит примерно так:

class Thing:
    def __init__(self, attrs={}, **kwargs):
        for arg in kwargs:
            attrs[arg] = kwargs[arg]
        self.attrs = attrs

Так что Thing({'color':'red'}) делает то же самое, что и Thing(color='red').

Моя проблема в том, что конструктор каким-то образом запоминает последнее переданное ему значение attrs.

Например:

>>> thing1 = Thing(color='red')
>>> thing2 = Thing()
>>> thing2.attrs
{'color': 'red'}

... но thing2.attrs должно быть пустым диктом!{}

Это заставило меня задуматься, не является ли это проблемой с использованием обоих **kwargs и аргумента, подобного attrs={}.

Есть идеи?

Ответы [ 5 ]

11 голосов
/ 22 июля 2011

Проблема с использованием аргументов по умолчанию заключается в том, что фактически существует только один их экземпляр. Когда вы говорите attrs={} в определении метода init , этот единственный экземпляр по умолчанию {} является значением по умолчанию для каждого вызова этого метода (он не делает новый по умолчанию пустой dict каждый раз, он использует то же самое).

Проблема в том, что если существует только один attrs, а затем для каждого экземпляра Thing, который вы говорите self.attrs = attrs, переменная-член self.attrs для каждого отдельного экземпляра указывает на один общий экземпляр по умолчанию. attrs.

Другой вопрос, не является ли это полностью избыточным? Вы можете использовать **kwargs для передачи ключевых слов / значений, аргументов или словаря. Если вы только что определили это:

class Thing:
    def __init__(self, **kwargs):
        for arg in kwargs:
            self.attrs[arg] = kwargs[arg]

Все эти стратегии все еще работают:

thing1 = Thing(color='red')

thing2 = Thing(**{'color':'red'})

my_dict = {'color' : 'red'}
thing3 = Thing(**my_dict)

Так что, если вы просто определите и используете Thing таким образом, вы полностью избежите своей проблемы.

2 голосов
/ 22 июля 2011

Как насчет изменения подписи, чтобы словарь создавался каждый раз

class Thing:
    def __init__(self, attrs=None, **kwargs):
        self.attrs = attrs or {}
        self.attrs.update(kwargs)
1 голос
/ 22 июля 2011

Как раз того, чего оно стоит - мы можем избежать проблемы «attrs является общим изменяемым объектом», просто не изменяя ее.Вместо того, чтобы выбросить kwargs в attrs, бросьте их обоих в новый диктат.Тогда default-arguments-object всегда будет {}.

class Thing:
    def __init__(self, attrs = {}, **kwargs):
        self.attrs = {}
        # Don't write the loop yourself!
        self.attrs.update(attrs)
        self.attrs.update(kwargs)

. Я упоминаю об этом только потому, что все торопятся описать идиому «используйте None в качестве аргумента по умолчанию и проверьте его», чтоЯ лично считаю довольно хакерской.У sgusc правильная идея: все усилия бесполезны, учитывая общую удивительность Python **kwargs.:)

1 голос
/ 22 июля 2011

Вы хотите изменить свой код на:

class Thing:
    def __init__(self, attrs=None, **kwargs):
        attrs = {} if attrs is None else attrs
        for arg in kwargs:
            attrs[arg] = kwargs[arg]
        self.attrs = attrs

Как уже отмечали другие, значение аргумента по умолчанию оценивается один раз, во время определения, а не каждый раз, когда вызывается функция.При использовании изменяемого контейнера каждое добавление к контейнеру просматривается всеми последующими вызовами, поскольку каждый вызов использует один и тот же контейнер в качестве значения по умолчанию.

Возможно, вы когда-либо использовали attrs только как способ предоставления начальногоценности, и вы никогда не хотели делиться диктатами вообще.В этом случае используйте это:

class Thing:
    def __init__(self, attrs=None, **kwargs):
        self.attrs = {}
        if attrs:
            self.attrs.update(attrs)
        for arg in kwargs:
            self.attrs[arg] = kwargs[arg]
1 голос
/ 22 июля 2011

attrs является ссылкой на словарь. Когда вы создаете новый объект, self.attrs указывает на этот словарь. Когда вы присваиваете значение из kwargs, оно попадает в этот словарь.

Теперь, когда вы создаете второй экземпляр, он self.attrs также указывает на тот же словарь. Таким образом, он получает все данные из этого словаря.

Для хорошего обсуждения этого см. «Наименьшее изумление» в Python: изменяемый аргумент по умолчанию здесь на stackoverflow. Также см. Значения параметров по умолчанию в Python для effbot .

...