Python类成员初始化

49 投票
5 回答
56560 浏览
提问于 2025-04-15 11:36

我最近在使用Python时遇到了一个小bug。这是一个很常见的新手错误,但让我对Python的机制产生了一些思考(我之前一直用C++,现在才开始接触Python)。我会把出问题的代码列出来,解释我怎么修复它,然后我还有几个问题想问……

场景是这样的:我有一个叫A的类,它里面有一个字典数据成员,下面是它的代码(当然这是简化版):

class A:
    dict1={}

    def add_stuff_to_1(self, k, v):
        self.dict1[k]=v

    def print_stuff(self):
        print(self.dict1)

使用这段代码的类是B:

class B:

    def do_something_with_a1(self):
        a_instance = A()
        a_instance.print_stuff()        
        a_instance.add_stuff_to_1('a', 1)
        a_instance.add_stuff_to_1('b', 2)    
        a_instance.print_stuff()

    def do_something_with_a2(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('c', 1)
        a_instance.add_stuff_to_1('d', 2)    
        a_instance.print_stuff()

    def do_something_with_a3(self):
        a_instance = A()    
        a_instance.print_stuff()            
        a_instance.add_stuff_to_1('e', 1)
        a_instance.add_stuff_to_1('f', 2)    
        a_instance.print_stuff()

    def __init__(self):
        self.do_something_with_a1()
        print("---")
        self.do_something_with_a2()
        print("---")
        self.do_something_with_a3()

注意,每次调用do_something_with_aX()时,都会初始化一个新的“干净”的A类实例,并在添加之前和之后打印字典。

这个bug(如果你还没发现的话):

>>> b_instance = B()
{}
{'a': 1, 'b': 2}
---
{'a': 1, 'b': 2}
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
---
{'a': 1, 'c': 1, 'b': 2, 'd': 2}
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2}

在第二次初始化A类时,字典并不是空的,而是带着上一次初始化的内容,依此类推。我本以为它们应该是“全新”的。

解决这个“bug”的办法显而易见,就是在:

self.dict1 = {}

A类的__init__构造函数中添加这行代码。不过,这让我产生了一些疑问:

  1. 在dict1声明的地方(A类的第一行)初始化“dict1 = {}”是什么意思?这没有意义吗?
  2. 是什么机制导致实例化时从上一次初始化中复制了引用?
  3. 如果我在构造函数中添加“self.dict1 = {}”(或者其他数据成员),那怎么不会影响之前初始化的实例中的字典成员呢?

编辑:根据回答,我现在明白了,如果我声明一个数据成员但没有在__init__或其他地方用self.dict1来引用它,我实际上是在定义一个在C++/Java中称为静态数据成员的东西。通过调用self.dict1,我让它“与实例绑定”。

5 个回答

1

@Matthew:请看看面向对象编程中类成员和对象成员的区别。这个问题出现的原因是,原始字典的声明使它成为了类成员,而不是对象成员(这正是提问者的本意)。因此,它在类的所有实例中只存在一次(也就是说,类本身作为一个对象的成员共享这个字典),所以这种行为是完全正确的。

2

当你访问一个实例的属性,比如说自定义的属性self.foo,Python会先在这个实例的字典里找,也就是self.__dict__。如果找不到,Python就会去类的字典里找,也就是TheClass.__dict__

在你的例子里,dict1是类A的定义,不是它的实例。

62

你一直说的这个问题,其实是Python类的正常行为,在文档中有说明。

你最开始在__init__外面声明一个字典,这样做是创建了一个类级别的变量。这个变量只会在最开始创建一次,以后每次你创建新对象时,都会使用同一个字典。如果你想要创建实例变量,就需要在__init__里用self来声明;就这么简单。

撰写回答