Python类成员初始化

Python Class Members Initialization

我最近刚在Python中与一只虫子作斗争。这是一个愚蠢的新手错误,但它让我想到了Python的机制(我是一个长时间的C++程序员,新的Python)。我将列出错误代码并解释我是如何修复的,然后我有几个问题…

场景:我有一个名为a的类,它有一个字典数据成员,下面是它的代码(这当然是简化的):

1
2
3
4
5
6
7
8
class A:
    dict1={}

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

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

使用此代码的类是B类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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(如果你还没有发现的话):

1
2
3
4
5
6
7
8
9
>>> 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的第二次初始化中,字典不是空的,而是从上次初始化的内容开始,依此类推。我希望他们开始"新鲜"。

解决这个"错误"的办法显然是增加:

1
self.dict1 = {}

在类A的__init__构造函数中,这让我感到奇怪:

  • "dict1="初始化在dict1声明点(类A中的第一行)的含义是什么?它毫无意义?
  • 导致从上次初始化复制引用的实例化机制是什么?
  • 如果我在构造函数(或任何其他数据成员)中添加"self.dict1=",它将如何影响先前初始化实例的字典成员?
  • 编辑:根据答案,我现在知道,通过声明一个数据成员,而不是在EDCOX1 1中或其他地方作为SELF.DIS1引用它,我实际上定义了C++/Java中所谓的静态数据成员。通过将其命名为self.dict1,我将其命名为"实例绑定"。


    您一直提到的bug是有文档记录的、标准的Python类行为。

    像最初那样在__init__之外声明dict就是声明一个类级变量。它一开始只创建一次,每次创建新对象时,它都会重复使用相同的指令。要创建实例变量,可以使用__init__中的self来声明它们;这就这么简单了。


    @马修:请回顾一下面向对象编程中类成员和对象成员的区别。这个问题的发生是因为原始dict的声明使它成为类成员,而不是对象成员(正如原始海报的意图一样),因此,它对于类的所有实例(即类本身,作为类对象本身的成员)都存在一次(共享一次),所以行为是完全正确的。


    当您访问实例的属性时,比如self.foo,python将首先在self.__dict__中找到'foo'。如果找不到,python将在TheClass.__dict__中找到'foo'

    在你的例子中,dict1属于A级,而不是实例。


    如果这是您的代码:

    1
    2
    3
    class ClassA:
        dict1 = {}
    a = ClassA()

    然后您可能期望这发生在python中:

    1
    2
    3
    4
    5
    6
    7
    class ClassA:
        __defaults__['dict1'] = {}

    a = instance(ClassA)
    # a bit of pseudo-code here:
    for name, value in ClassA.__defaults__:
        a.<name> = value

    据我所知,这就是所发生的事情,只是dict复制了指针,而不是值,这是Python中所有地方的默认行为。查看此代码:

    1
    2
    3
    4
    a = {}
    b = a
    a['foo'] = 'bar'
    print b

    pythons类声明作为代码块执行,任何局部变量定义(函数定义是一种特殊类型)都存储在构造的类实例中。由于属性查找在Python中的工作方式,如果在实例上找不到属性,则使用类上的值。

    这是一篇关于Python博客历史上的类语法的有趣文章。