可能重复:
“Least Astonishment” in Python: The Mutable Default Argument
我今天下午写了一些代码,偶然发现了代码中的一个错误。我注意到我的一个新创建的对象的默认值是从另一个对象转移的!例如:
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!
所以我知道这可以解决这个问题:
class One(object):
def __init__(self, my_list=None):
self.my_list = my_list if my_list is not None else []
我想知道的是......为什么?为什么Python类是结构化的,以便在类的实例中保存默认值?
提前致谢!
答案 0 :(得分:9)
这是Python默认值工作方式的已知行为,这对于不警惕的人来说通常是令人惊讶的。空数组对象[]
是在函数的定义时创建的,而不是在调用时创建的。
要解决此问题,请尝试:
def __init__(self, my_list=None):
if my_list is None:
my_list = []
self.my_list = my_list
答案 1 :(得分:4)
其他几位人士指出,这是Python中“可变默认参数”问题的一个实例。基本原因是默认参数必须存在于函数“外部”才能传递给函数。
但是,作为问题的真正根源是与默认参数无关。如果修改了可变的默认值,那么任何时候它都会很糟糕,你真的需要问自己:如果一个明确提供的值被修改会不会很糟糕?除非有人非常熟悉你班级的内容,否则以下行为也会非常令人惊讶(因此会导致错误):
>>> 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
的“模式”,那么您不应该这样做。你应该不修改你的论点!参数不应被视为拥有被调用代码,除非明确记录为拥有它们。
在这种情况下(特别是因为你正在初始化一个类实例,所以可变变量将会存在很长时间并被其他方法使用,并且可能还有其他代码从实例中检索它)我会做以下:
class One(object):
def __init__(self, my_list=[])
self.my_list = list(my_list)
现在,您要从作为输入提供的列表中初始化您的班级数据,而不是取得已有列表的所有权。没有危险,两个单独的实例最终共享相同的列表,也不会与调用者可能想要继续使用的调用者中的变量共享该列表。它还具有很好的效果,您的调用者可以提供元组,生成器,字符串,集合,字典,自制的自定义可迭代类等,并且您知道仍然可以依靠具有append
方法的self.my_list,因为你是自己做的。
这里仍然存在一个潜在的问题,如果列表中包含的元素本身是可变的,那么调用者和此实例仍然可能会意外地相互干扰。我发现在我的代码中实际上并不常常出现问题(所以我不会自动对所有内容进行深层复制),但你必须要注意它。
另一个问题是,如果my_list可能非常大,那么副本可能很昂贵。在那里你必须做出权衡。在这种情况下,最好只使用传入列表,并使用if my_list is None: my_list = []
模式来阻止所有默认实例共享一个列表。但是如果你这样做,你需要在文档或类的名称中明确说明调用者放弃他们用来初始化实例的列表的所有权。或者,如果你真的想构建一个仅仅为了包含在One
实例中的列表,也许你应该弄清楚如何封装里面的列表的创建初始化One
,而不是先构建它;毕竟,它实际上是实例的一部分,而不是初始值。有时候这不够灵活。
有时候你真的确实想要进行别名,并通过改变他们都有权访问的值来进行代码通信。然而,在我做出这样的设计之前,我认为非常努力。并且它会让其他人感到惊讶(当你在X个月回到代码时,你也会这样),所以再次提供文档是你的朋友!
在我看来,教育新的Python程序员关于“可变的默认参数”问题实际上(轻微)有害。我们应该问他们“为什么要修改你的论点?” (并且然后指出默认参数在Python中的工作方式)。具有合理默认参数的函数的事实通常是一个很好的指示,它不是意图作为接收预先存在的值的所有权的东西,因此它可能不应该修改参数是否具有默认值。
答案 2 :(得分:3)
这是Python中任何地方的默认参数的标准行为,而不仅仅是在类中 有关详细说明,请参阅Mutable defaults for function/method arguments。
答案 3 :(得分:3)
基本上,python函数对象存储一个默认参数的元组,这对于整数等不可变的东西很好,但是列表和其他可变对象经常就地修改,导致你观察到的行为。
答案 4 :(得分:2)
Python函数是对象。函数的默认参数是该函数的属性。因此,如果参数的默认值是可变的并且在函数内部进行了修改,则更改将反映在对该函数的后续调用中。
答案 5 :(得分:1)
不是答案,但值得注意的是,对于在任何类函数之外定义的类变量,也为真。
示例:
>>> class one:
... myList = []
...
>>>
>>> one1 = one()
>>> one1.myList
[]
>>> one2 = one()
>>> one2.myList.append("Hello Thar!")
>>>
>>> one1.myList
['Hello Thar!']
>>>
请注意,myList
的值不仅会持续存在,而且myList
的每个实例都会指向相同的列表。
我自己遇到了这个错误/功能,花了3个小时试图弄清楚发生了什么。当你获得有效数据时调试是相当具有挑战性的,但它不是来自本地计算,而是先前的计算。
它变得更糟,因为这不仅仅是一个默认参数。你不能只将myList
放在类定义中,将设置为等于某个东西,尽管设置的等于仅仅一次
至少在我看来,解决方案是在__init__
内创建所有类变量。