Python基类共享属性?

16

test.py中的代码:

class Base(object):
    def __init__(self, l=[]):
        self.l = l

    def add(self, num):
        self.l.append(num)

    def remove(self, num):
        self.l.remove(num)

class Derived(Base):
    def __init__(self, l=[]):
        super(Derived, self).__init__(l)

Python命令行会话:

Python 2.6.5 (r265:79063, Apr  1 2010, 05:22:20) 
[GCC 4.4.3 20100316 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import test
>>> a = test.Derived()
>>> b = test.Derived()
>>> a.l
[]
>>> b.l
[]
>>> a.add(1)
>>> a.l
[1]
>>> b.l
[1]
>>> c = test.Derived()
>>> c.l
[1]
我原本期望的是 "类似于C++" 的行为,即每个派生对象都包含其自己的基类实例。这仍然是这种情况吗?为什么每个对象看起来共享相同的列表实例?
2个回答

20

您犯了一个Python新手常见的错误。

请看我在这里的回答: 如何在Python中声明实例变量的默认值?

简单来说,Python只会解释一次类定义。也就是说,在__init__()方法中声明的所有内容仅被创建一次。换句话说,您的[]列表默认参数只会被创建一次。

因此,每当您创建一个新类时,self.l = l都会将一个引用分配给同一个实例,这解释了您不希望出现的行为。

Pythonic的方式是这样的(部分代码):

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

此外,你应该考虑使用一个比l更好的命名规则,因为l很难阅读,可能会被误认为是1|


+1 对于正确答案,但至少简要地在这里解释一下。 - Etaoin
显然,这是一个测试片段。我永远不会选择'l'作为变量名。 - tad
好的,很高兴澄清了这一点。 - Xavier Ho
我会用更简单的方式分配arg,就像这样: def __init__(self, arg=None): self.arg = arg or [] - Olivier Maurel

2
这被称为可变默认参数错误,通常由新手在Python中犯。当您给出一个可变的默认参数时,当需要使用默认参数时,相同的对象会在实例间使用。要了解更多请查看“重要警告”部分 http://docs.python.org/tutorial/controlflow.html#default-argument-values

在您的代码中,实例a在其init调用中使用了可变默认参数(一个空列表对象),当您创建了b实例并调用Base的init方法时,再次使用与a在其init中使用的完全相同的对象。换句话说,a.l和b.l指向同一个列表对象。

一个非常类似的讨论:“最小惊奇”和可变默认参数


这不是一个错误,而是语言的工作方式。 - Ned Batchelder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接