Python类和实例属性混淆

时间:2012-05-31 12:09:24

标签: python

  

可能重复:
  “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

5 个答案:

答案 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__,它可能会轻易咬你。问题实际上是你正在改变一个输入参数(通过在实例属性中存储对它的引用,然后你可以改变它),这绝不是一个好主意,除非突变对象是目的功能。这通常不适用于构造函数,因此如果您打算对其进行变更,则应该复制该列表。