可能重复:
“Least Astonishment” in Python: The Mutable Default Argument
上课
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
答案 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
答案 1 :(得分:1)
这是python函数中的一个小怪癖,在没有类的情况下也可以看到:
def foo(bar=[]):
bar.append('boo')
print bar
foo()
foo()
“问题”是在加载模块时创建默认参数(bar)。 如果您没有明确传递其他内容,则继续将相同的对象作为foo的默认参数传递。
使用可变的默认参数的规范方法是使用sentinel值(通常为None
),可以使用is
运算符对其进行测试,以指示用户未传递任何内容(除非当然,在你的函数中需要改变默认参数)。 e.g:
def foo(bar=None):
if(bar is None):
bar=[]
bar.append('boo')
print bar
这是文档的link - 密切注意“重要警告”部分。
答案 2 :(得分:1)
用作参数messages
的默认值的空列表是全局变量。因此,在您的第一个示例中,foo.messages is bar.messages
为True,而在您的第二个示例中,您为messages=["Bars message"]
,导致bar.messages is not foo.messages
为True。这是最经典的陷阱!
答案 3 :(得分: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
[]
答案 4 :(得分:0)
不,与类属性和实例属性无关。
麻烦的是,Python中的所有赋值都只是绑定对象的引用,因此无论使用什么值作为messages
的{{1}}参数,最终都是存储为__init__
的值属性;它没有被复制。当该值也在其他地方使用并且可以进行变异时,这是一个问题,因为messages
属性引用的对象的更改最终会影响对同一对象的其他引用。
由于messages
参数的默认值为messages
,因此每次调用它而不提供值时,都会获得相同的对象。默认值为默认值值< / strong>,而不是用于创建新值的配方,因此每次使用默认值时,您都会获得相同的列表。
人们将此称为“可变默认参数”问题,但我认为它比这更普遍。如果您明确传递__init__
,它可能会轻易咬你。问题实际上是你正在改变一个输入参数(通过在实例属性中存储对它的引用,然后你可以改变它),这绝不是一个好主意,除非突变对象是目的功能。这通常不适用于构造函数,因此如果您打算对其进行变更,则应该复制该列表。