2012-05-31 4 views
1

Можно создать дубликат:
“Least Astonishment” in Python: The Mutable Default Argumentкласс Python и экземпляр атрибута спутанность

Для класса

class ValidationResult(): 
    def __init__(self, passed=True, messages=[], stop=False): 
     self.passed = passed 
     self.messages = messages 
     self.stop = stop 

работает

foo = ValidationResult() 
bar = ValidationResult() 
foo.messages.append("Foos message") 
print foo.messages 
print bar.messages 

производит

['Foos message'] 
['Foos message'] 

пока это

foo = ValidationResult() 
bar = ValidationResult(messages=["Bars message"]) 
foo.messages.append("Foos message") 
print foo.messages 
print bar.messages 

производит

['Foos message'] 
['Bars message'] 

Я думаю, что я пропустил лодку на понимании атрибутов объекта здесь. В первом примере я ожидал, что Foos message применим только к foo. Каков правильный способ объявить атрибут объекта, только изменяемый его экземпляром?

Использование Python 2.7.1

+3

Есть много вопросов об этом. Не используйте изменяемые объекты в качестве аргумента по умолчанию для функции ... – JBernardo

+3

http://stackoverflow.com/questions/2639915/why-the-mutable-default-argument-fix-syntax-is-so-ugly-asks-python -newbie http://stackoverflow.com/questions/1132941/least-astonishment-in-python-the-mutable-default-argument и т. д. – kennytm

ответ

1

Это немного причуда функций питона и их можно увидеть так же, как и без класса:

def foo(bar=[]): 
    bar.append('boo') 
    print bar 

foo() 
foo() 

«Проблема» в том, что аргумент по умолчанию (бар) создается при загрузке модуля. Тот же объект по-прежнему передается как аргумент по умолчанию foo, если вы явно не передаете что-то еще.

Канонический способ использования аргументов по умолчанию, которые являются изменяемыми, - это использовать значение для оповещения (обычно None), которое может быть проверено с помощью оператора is, чтобы указать, что пользователь ничего не пропускал (если не требуется изменение аргумента по умолчанию в ваша функция, конечно). например .:

def foo(bar=None): 
    if(bar is None): 
     bar=[] 
    bar.append('boo') 
    print bar 

Вот link к документации - обратить пристальное внимание на раздел «Важное предупреждение».

+0

Итак, что произойдет, если я назову 'bar = ValidationResult (messages = foo.messaged)' ? Корневая проблема заключается в владении параметрами, а не аргументами по умолчанию. Если вы не мутируете свои входы, использование '[]' по умолчанию вполне безопасно. – Ben

+0

@Ben - Я не могу придумать какую-либо хорошую ситуацию, когда у вас будет пустой список в качестве аргумента по умолчанию для функции, а затем ничего не сделайте. Даже если вы не измените его в функции, но вернете, вы можете получить очень удивительное поведение позже. Если пользователь хочет передать другой список и иметь несколько ссылок на тот же список, что и их. – mgilson

+0

@Ben - Я добавил предложение о том, что если вы не используете трюк 'if mutable_arg is None', если вы действительно хотите мутировать' mutable_arg', но я предполагал, что это было очевидно. – mgilson

1

Пустой список, используемый как значение по умолчанию аргумента messages, является глобальной переменной. Таким образом, в вашем первом примере foo.messages is bar.messages является True, тогда как в вашем втором примере вы messages=["Bars message"], в результате чего bar.messages is not foo.messages является True. Это самая классическая ловушка!

0

Этот self.messages является псевдонимом параметра по умолчанию. Параметр по умолчанию создается с помощью функции, а не с вызывающим.

class ValidationResult(): 
    def __init__(self, passed=True, messages=None, stop=False): 
     self.passed = passed 
     self.messages = messages if messages is not None else [] 
     self.stop = stop 

>>> foo = ValidationResult() 
>>> bar = ValidationResult() 
>>> foo.messages.append("Foos message") 
>>> print foo.messages 
['Foos message'] 
>>> print bar.messages 
[] 
+0

параметр по умолчанию относится к функции в классе, чтобы быть более точным. – jamylak

+1

Незначительная точка, вместо проверки, если 'messages! = None', ваш тест, вероятно, должен быть' messages is not None'. – mgilson

+0

Приветствия для комментариев. Сделаны оба изменения. –

2

Вы можете увидеть, что здесь происходит:

>>> class ValidationResult(): 
...  def __init__(self, passed=True, messages=[], stop=False): 
...   self.passed = passed 
...   self.messages = messages 
...   print id(self.messages) 
...   self.stop = stop 
... 
>>> foo = ValidationResult() 
4564756528 
>>> bar = ValidationResult() 
4564756528 

по умолчанию аргумент всегда тот же объект в памяти.Один быстрый обходной путь для списков, чтобы создать копию списка для каждого экземпляра:

>>> class ValidationResult(): 
...  def __init__(self, passed=True, messages=[], stop=False): 
...   self.passed = passed 
...   self.messages = messages[:] 
...   print id(self.messages) 
...   self.stop = stop 
... 
>>> foo = ValidationResult() 
4564756312 
>>> bar = ValidationResult() 
4564757032 
+0

Я никогда не думал о создании копии - это хорошая идея, если пользователь не ожидает, что этот список будет тем же самым списком в ValidationResult. например 'MyList = [ 'Foo']; v = ValidationResult (сообщения = MyList); mylist - v.messages #False, Huh? '- но это, вероятно, обычно не ожидаемое поведение ... – mgilson

0

Нет, ничего общего с классом Атрибуты против атрибутов экземпляра.

Проблема заключается в том, что все присваивание в Python является просто обязательными ссылками на объекты, поэтому любое значение было использовано в качестве параметра __init__messages заканчивает тем, что значение, которое хранится в качестве атрибута messages; он не копируется. Это проблема, когда это значение также используется в другом месте и может быть мутировано, поскольку в результате изменения объекта, на который ссылается атрибут messages, влияют на другие ссылки на один и тот же объект.

Поскольку у вас есть значение по умолчанию для параметра __init__messages, каждый раз, когда вы называете это без предоставления значения вы получите тот же объект для заполнения. Значения по умолчанию по умолчанию значения, а не рецепты для создания новых значений, так каждый раз, когда используется значение по умолчанию, вы получаете тот же список.

Люди ссылаются на эту проблему как на «изменяемый аргумент по умолчанию», но я думаю, что это более общее, чем это. Это может легко укусить вас, если вы явно передаете messages. Беда в том, что вы мутируете входной параметр (путем хранения ссылки на него в атрибуте экземпляра, который вы затем мутируете), что никогда не является хорошей идеей, если мутирующий объект не является целью функции. Обычно это не относится к конструктору, поэтому вам нужно скопировать список, если вы планируете его мутировать.

Смежные вопросы