Python类函数的默认变量是类对象吗?

2024-09-24 22:27:53 发布

您现在位置:Python中文网/ 问答频道 /正文

Possible Duplicate:
“Least Astonishment” in Python: The Mutable Default Argument

今天下午我在写一些代码,在我的代码中偶然发现了一个bug。我注意到我新创建的一个对象的默认值是从另一个对象继承过来的!例如:

class One(object):
    def __init__(self, my_list=[]):
        self.my_list = my_list

one1 = One()
print(one1.my_list)
[] # empty list, what you'd expect.

one1.my_list.append('hi')
print(one1.my_list)
['hi'] # list with the new value in it, what you'd expect.

one2 = One()
print(one2.my_list)
['hi'] # Hey! It saved the variable from the other One!

所以我知道可以通过这样做来解决:

^{pr2}$

我想知道的是。。。为什么?为什么Python类的结构是在类的实例之间保存默认值?在

提前谢谢!在


Tags: the对象代码inselfyoumyhi
3条回答

这是Python默认值工作方式的一种已知行为,这通常会让粗心的人感到惊讶。空数组对象[]是在函数的定义时创建的,而不是在调用时创建的。在

要修复它,请尝试:

def __init__(self, my_list=None):
    if my_list is None:
        my_list = []
    self.my_list = my_list

其他一些人指出,这是Python中“可变默认参数”问题的一个实例。基本原因是默认参数必须存在于函数的“外部”才能传递给它。在

但真正的根源是一个问题与默认参数无关。任何时候,如果修改一个可变的默认值是不好的,你真的需要问问自己:如果一个显式提供的值被修改了,它会是坏的吗?除非有人非常熟悉您的类的内部结构,否则以下行为也会非常令人惊讶(因此会导致bug):

>>> class One(object):
...     def __init__(self, my_list=[]):
...         self.my_list = my_list
...
>>> alist = ['hello']
>>> one1 = One(alist)
>>> alist.append('world')
>>> one2 = One(alist)
>>> 
>>> print(one1.my_list) # Huh? This isn't what I initialised one1 with!
['hello', 'world']
>>> print(one2.my_list) # At least this one's okay...
['hello', 'world']
>>> del alist[0]
>>> print one2.my_list # What the hell? I just modified a local variable and a class instance somewhere else got changed?
['world']

10次中有9次,如果您发现自己在使用None作为默认值并使用if value is None: value = default的“模式”,那么您不应该这样做。你不应该修改你的论点!参数不应该被视为被调用代码拥有的,除非它被明确地记录为拥有它们。在

在这种情况下(尤其是因为您正在初始化一个类实例,所以可变变量将生存很长时间,并被其他方法和可能从实例中检索它的其他代码使用)我将执行以下操作:

^{pr2}$

现在,您将从作为输入提供的列表初始化类的数据,而不是获取预先存在的列表的所有权。两个独立的实例最终共享同一个列表,也不存在与调用方可能希望继续使用的调用方中的变量共享该列表的危险。它还有一个很好的效果,你的调用者可以提供元组、生成器、字符串、集合、字典、自制的自定义iterable类等,而且你知道你仍然可以依赖它self.my_列表有一个append方法,因为这是你自己做的。在

这里仍然存在一个潜在的问题,如果列表中包含的元素本身是可变的,那么调用者和这个实例仍然会意外地相互干扰。我发现在我的代码中,这并不是一个经常出现的问题(所以我不会自动对所有东西进行深入的复制),但是你必须意识到这一点。在

另一个问题是,如果我的清单可能非常大,副本可能是昂贵的。在那里你必须做出权衡。在这种情况下,也许最好使用传入列表,并使用if my_list is None: my_list = []模式来防止所有默认实例共享同一个列表。但是,如果您这样做,您需要在文档或类的名称中明确指出,调用方正在放弃它们用于初始化实例的列表的所有权。或者,如果您真的只想构建一个列表来封装一个One的实例,也许您应该找出如何将列表的创建封装在One的初始化中,而不是先构造它;毕竟,它实际上是实例的一部分,而不是初始化值。但有时这不够灵活。在

有时候你真的想让别名继续存在,让代码通过改变它们都可以访问的值来进行通信。然而,在我致力于这样的设计之前,我想得很仔细。而且它会让其他人(还有你在X个月后回到代码中时)感到惊讶,所以文档也是你的朋友!在

在我看来,教育新的Python程序员“可变的默认参数”实际上是有害的。我们应该问他们“你为什么要修改你的论点?”(然后指出Python中默认参数的工作方式)。不管这个参数的值是否是一个很好的参数,它通常都不应该被修改。在

基本上,python函数对象存储一个默认参数的元组,这对于诸如整数之类的不可变的东西来说是很好的,但是列表和其他可变对象经常被适当地修改,从而导致您所观察到的行为。在

相关问题 更多 >