Python类列表在类实例初始化时应该是空的,但事实并非如此.为什么?

can*_*nin 5 python initialization list

我想创建一个类的实例,其中包含默认为空的列表; 而不是以后将此列表设置为最终完整列表,我想连续添加项目.这是一段示例代码,说明了这一点:

#!/usr/bin/python

class test:
    def __init__(self, lst=[], intg=0):
        self.lista   = lst
        self.integer = intg

name_dict = {}
counter   = 0

for name in ('Anne', 'Leo', 'Suzy'):
    counter += 1

    name_dict[name] = test()
    name_dict[name].integer += 1
    name_dict[name].lista.append(counter)

    print name, name_dict[name].integer, name_dict[name].lista
Run Code Online (Sandbox Code Playgroud)

当我运行上述程序时,我希望得到

安妮1 [1]
狮子座1 [2]
Suzy 1 [3]

因为我假设lista总是初始化为空列表.

我得到的是这样的:

安妮1 [1]
狮子座1 [1,2]
Suzy 1 [1,2,3]

如果我更换self.lista = lstself.lista = []它工作正常,当我添加行就像name_dict[name].lista = []for循环.

为什么保留先前对象列表的内容,但它们的值integer不是?我对Python很陌生,所以如果有人可以向我指出我的想法/假设误入歧途,那将会很棒.

非常感谢您的回复.

Mar*_*ers 12

使用可变对象作为默认值是一个非常糟糕的主意,就像在这里一样:

def __init__(self, lst=[], intg=0):
     # ...
Run Code Online (Sandbox Code Playgroud)

把它改成这个:

def __init__(self, lst=None, intg=0):
     if lst is None:
         lst = []
     # ...
Run Code Online (Sandbox Code Playgroud)

您的版本不起作用的原因是在定义函数时只创建一次空列表,而不是每次调用函数时都创建.

在一些Python实现中,您可以通过检查以下值来func_defaults查看函数默认值的值:

print test.__init__.func_defaults
name_dict[name] = test()
# ...
Run Code Online (Sandbox Code Playgroud)

输出:

([],)
Anne 1 [1]
([1],)
Leo 1 [1, 2]
([1, 2],)
Suzy 1 [1, 2, 3] 

  • @ keegan3d:那不太一样.如果用户提供了一个空列表,那么您的版本将不会使用该列表,而是创建一个新列表,因为空列表是"虚假". (3认同)

mar*_*cog 7

问题在于这一行:

def __init__(self, lst=[], intg=0):
Run Code Online (Sandbox Code Playgroud)

您不应该使用列表作为默认参数。第一次__init__在没有lst指定的情况下调用 Python 解释器将定义一个空列表[]。如果未指定,对该函数的后续调用将对同一列表进行操作lst,而不声明新列表。这会导致奇怪的问题。

您应该改为使用默认值None并在函数开头添加一个检查:

def __init__(self, lst=None, intg=0):
    if lst is None:
        lst = []
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅此帖子。引用帖子:

默认参数在函数定义时进行评估,因此它们在调用之间是持久的。这有一些有趣(且令人困惑)的副作用。一个例子:

>>> def foo(d=[]):
...     d.append('a')
...     return d
Run Code Online (Sandbox Code Playgroud)

如果你以前没有尝试过,你可能希望 foo 总是返回 ['a']:它应该以一个空列表开始,在它后面附加 'a',然后返回。这是它的实际作用:

>>> foo() ['a']
>>> foo() ['a', 'a']
>>> foo() ['a', 'a', 'a']
Run Code Online (Sandbox Code Playgroud)

这是因为默认值d是在创建函数时分配的,而不是在调用时分配的。每次调用该函数时,该值仍与上次调用有关。如果您将线程放入混合中,这会变得更加奇怪。如果两个不同的线程同时执行该函数,并且其中一个更改了默认参数,则它们都会看到更改。

当然,所有这些只有在默认参数的值是可变类型时才成立。如果我们改变foo为定义为

>>> def foo2(d=0):
...     d += 1
...     return d
Run Code Online (Sandbox Code Playgroud)

那么它将总是返回 1。(这里的区别在于foo2,变量d被重新赋值,而变量foo的值被改变。)