1

可能的重复:
Python 中的“Least Astonishment”:可变默认参数

上课

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

4

5 回答 5

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
于 2012-05-31T12:18:02.183 回答
1

用作参数默认值的空列表messages是一个全局变量。因此,在您的第一个示例中,是 True,而foo.messages is bar.messages在您的第二个示例中,您是 True。这是最经典的一个陷阱!messages=["Bars message"]bar.messages is not foo.messages

于 2012-05-31T12:16:32.263 回答
1

这是 python 函数中的一个小怪癖,在没有类的情况下也可以看到:

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

foo()
foo()

“问题”是加载模块时创建默认参数(bar)。 如果您没有显式传递其他内容,则将继续传递相同的对象作为 foo 的默认参数。

使用可变默认参数的规范方法是使用标记值(通常是None),可以使用is运算符对其进行测试,以指示用户没有传递任何内容(当然,除非在您的函数中需要改变默认参数)。例如:

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

这是文档的链接——密切注意“重要警告”部分。

于 2012-05-31T12:14:21.897 回答
0

不,与类属性与实例属性无关。

问题在于,Python 中的所有赋值都只是简单地绑定对对象的引用,因此任何用作messages参数__init__的值最终都会成为存储为messages属性的值;它不是复制的。当该值也在其他地方使用并且可以改变时,这是一个问题,因为对messages属性引用的对象的更改最终会影响对同一对象的其他引用。

由于您有messages参数 to的默认值,因此__init__每次在不提供值的情况下调用它时都会填充相同的对象。默认值是默认,而不是用于创建新值的方法,因此每次使用默认值时,您得到相同的列表。

人们将此称为“可变默认参数”问题,但我认为它比这更普遍。如果你也明确通过messages,它很容易咬你。真正的问题是你正在改变一个输入参数(通过将一个对它的引用存储在一个实例属性中,然后你改变它),这绝不是一个好主意,除非改变对象是函数的目的。构造函数通常不是这样,所以如果你打算改变它,你应该复制列表。

于 2012-05-31T12:27:18.403 回答
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
[]
于 2012-05-31T12:17:13.847 回答