Python类成员初始化
我最近在使用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__
构造函数中添加这行代码。不过,这让我产生了一些疑问:
- 在dict1声明的地方(A类的第一行)初始化“dict1 = {}”是什么意思?这没有意义吗?
- 是什么机制导致实例化时从上一次初始化中复制了引用?
- 如果我在构造函数中添加“self.dict1 = {}”(或者其他数据成员),那怎么不会影响之前初始化的实例中的字典成员呢?
编辑:根据回答,我现在明白了,如果我声明一个数据成员但没有在__init__
或其他地方用self.dict1来引用它,我实际上是在定义一个在C++/Java中称为静态数据成员的东西。通过调用self.dict1,我让它“与实例绑定”。
5 个回答
@Matthew:请看看面向对象编程中类成员和对象成员的区别。这个问题出现的原因是,原始字典的声明使它成为了类成员,而不是对象成员(这正是提问者的本意)。因此,它在类的所有实例中只存在一次(也就是说,类本身作为一个对象的成员共享这个字典),所以这种行为是完全正确的。
当你访问一个实例的属性,比如说自定义的属性self.foo,Python会先在这个实例的字典里找,也就是self.__dict__
。如果找不到,Python就会去类的字典里找,也就是TheClass.__dict__
。
在你的例子里,dict1
是类A的定义,不是它的实例。
你一直说的这个问题,其实是Python类的正常行为,在文档中有说明。
你最开始在__init__
外面声明一个字典,这样做是创建了一个类级别的变量。这个变量只会在最开始创建一次,以后每次你创建新对象时,都会使用同一个字典。如果你想要创建实例变量,就需要在__init__
里用self
来声明;就这么简单。