Python类的函数默认变量是类对象吗?
可能重复:
Python中的“最小惊讶”:可变的默认参数
我今天下午在写代码,偶然发现了代码中的一个错误。 我注意到,我的一个新创建的对象的默认值是从另一个对象继承的! 例如:
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类结构化,以便默认值保存在类的实例中?
提前致谢!
还有一些人指出,这是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']
如果你发现自己已经达到了使用None
作为默认值的“模式”,并且if value is None: value = default
,那么你不应该这样做。 你应该只是不修改你的论点! 不应将参数视为被调用代码拥有,除非明确记录为拥有所有权。
在这种情况下(特别是因为你正在初始化一个类实例,所以可变变量会长时间地运行,并被其他方法以及可能从实例中检索到的其他代码所使用)我会执行以下操作:
class One(object):
def __init__(self, my_list=[])
self.my_list = list(my_list)
现在,您正在从作为输入提供的列表中初始化您班级的数据,而不是取得预先存在的列表的所有权。 没有任何危险,即两个单独的实例最终共享同一个列表,而且该列表与调用方中可能想要继续使用的变量共享。 它也有很好的效果,你的调用者可以提供元组,生成器,字符串,集合,字典,自制的自定义迭代类等,你知道你仍然可以依靠self.my_list有一个append
方法,因为你做到了你自己。
这里仍然存在潜在的问题,如果列表中包含的元素本身是可变的,那么调用者和这个实例仍然可能意外地相互干扰。 我发现在我的代码中实际上并不经常是一个问题(所以我不会自动获取所有内容的深层副本),但是您必须注意它。
另一个问题是,如果my_list可能非常大,则副本可能很昂贵。 你必须做一个权衡。 在这种情况下,最好使用传入列表,并使用if my_list is None: my_list = []
模式来防止所有默认实例共享一个列表。 但是,如果你这样做了,你需要在文档或类名称中明确说明,调用者放弃了用来初始化实例的列表的所有权。 或者,如果你真的想构建一个列表,仅仅是为了在One
一个实例中完成,也许你应该弄清楚如何在One
的初始化中封装列表的创建,而不是先构造列表; 毕竟,它确实是实例的一部分,而不是初始值。 有时候这还不够灵活。
有时候你确实希望有别名进行,并且通过改变他们都有权访问的值进行代码通信。 然而,在我承诺进行这样的设计之前,我觉得非常努力。 而且它会让其他人感到惊讶(当你在X个月内回到代码时),那么文档就是你的朋友!
在我看来,教育新的Python程序员关于“可变的默认参数”缺陷实际上(略微)有害。 我们应该问他们:“你为什么要修改你的论点?” (然后指出默认参数在Python中的工作方式)。 具有明智的默认参数的函数的事实往往是一个很好的指示器,它不是用来接收预先存在的值的所有权的东西,所以它可能不应该修改参数,不管它是否得到默认值。
这是Python默认值工作方式的一种已知行为,这对于不谨慎的人来说通常是令人惊讶的。 空数组object []
是在定义函数时创建的,而不是在调用它时。
要解决它,请尝试:
def __init__(self, my_list=None):
if my_list is None:
my_list = []
self.my_list = my_list
基本上,python函数对象存储默认参数的元组,这对于不可变的东西(如整数)来说很好,但列表和其他可变对象通常会在原地进行修改,从而导致您观察到的行为。
链接地址: http://www.djcxy.com/p/28523.html上一篇: Python class function default variables are class objects?
下一篇: python function default parameter is evaluated only once?