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?