Python 基类共享属性吗?

16 投票
2 回答
3849 浏览
提问于 2025-04-15 22:58

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 个回答

2

这被称为可变默认参数的错误,很多刚接触Python的人都会犯这个错误。当你把一个可变对象(比如列表)作为默认参数时,如果这个默认参数被多次使用,实际上会用到同一个对象。想要更深入了解,可以查看重要警告部分。

在你的代码中,实例a在初始化时使用了一个可变的默认参数(一个空列表),而当你创建实例b时,它又调用了Base的初始化方法,这时候又用了a在初始化时使用的那个同样的对象。简单来说,a.l和b.l指向的是同一个列表对象。

还有一个非常相似的讨论 - “最小惊讶原则”和可变默认参数

20

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

你可以看看我在这里的回答: 我应该如何为Python中的实例变量声明默认值?

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

然后self.l = l每次你创建一个新类时,都会指向同一个实例,这就是你没有预料到的行为。

Python推荐的做法是这样的(部分代码):

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

另外,你应该考虑使用比l更好的命名方式,因为l很难阅读,可能会被误认为是1|

撰写回答