关于类:为什么属性引用在Python继承中的作用是这样的?

Why do attribute references act like this with Python inheritance?

本问题已经有最佳答案,请猛点这里访问。

以下内容似乎很奇怪……基本上,somedata属性似乎在从the_base_class继承的所有类之间共享。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class the_base_class:
    somedata = {}
    somedata['was_false_in_base'] = False


class subclassthing(the_base_class):
    def __init__(self):
            print self.somedata


first = subclassthing()
{'was_false_in_base': False}
first.somedata['was_false_in_base'] = True
second = subclassthing()
{'was_false_in_base': True}
>>> del first
>>> del second
>>> third = subclassthing()
{'was_false_in_base': True}

__init__函数中定义self.somedata显然是解决这一问题的正确方法(因此每个类都有自己的somedatadict),但是什么时候需要这种行为?


没错,somedata在类的所有实例和它的子类之间共享,因为它是在类定义时创建的。台词

1
2
somedata = {}
somedata['was_false_in_base'] = False

在定义类时执行,即当解释器遇到EDCOX1×1语句时——而不是在创建实例时(在Java中考虑静态初始化块)。如果类实例中不存在属性,则将检查类对象的属性。

在类定义时,可以运行arbritrary代码,如下所示:

1
2
3
4
5
6
7
8
 import sys
 class Test(object):
     if sys.platform =="linux2":
         def hello(self):
              print"Hello Linux"
     else:
         def hello(self):
              print"Hello ~Linux"

在Linux系统上,Test().hello()将打印Hello Linux,在所有其他系统上,将打印其他字符串。

在constrast中,__init__中的对象是在实例化时创建的,并且仅属于该实例(当它们被分配给self时):

1
2
3
class Test(object):
    def __init__(self):
        self.inst_var = [1, 2, 3]

在许多情况下,在类对象而不是实例上定义的对象是有用的。例如,您可能希望缓存类的实例,以便共享具有相同成员值的实例(假定它们不可变):

1
2
3
4
5
6
7
8
9
10
class SomeClass(object):
    __instances__ = {}

    def __new__(cls, v1, v2, v3):
        try:
            return cls.__insts__[(v1, v2, v3)]
        except KeyError:
            return cls.__insts__.setdefault(
               (v1, v2, v3),
               object.__new__(cls, v1, v2, v3))

大多数情况下,我将类体中的数据与元类或通用工厂方法结合使用。


注意,您看到的行为部分是由于somedatadict,而不是像bool这样的简单数据类型。

例如,请参阅这个不同的示例,它的行为不同(尽管非常相似):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class the_base_class:
    somedata = False

class subclassthing(the_base_class):
    def __init__(self):
        print self.somedata


>>> first = subclassthing()
False
>>> first.somedata = True
>>> print first.somedata
True
>>> second = subclassthing()
False
>>> print first.somedata
True
>>> del first
>>> del second
>>> third = subclassthing()
False

这个例子与问题中给出的行为不同的原因是,这里的first.somedata被赋予了一个新的值(对象True),而在第一个例子中,first.somedata引用的dict对象(以及其他子类实例)正在被修改。

请参阅Torsten Marek对此答案的评论,以获得进一步的澄清。


我认为理解这一点(以便您能够预测行为)的最简单方法是认识到您的somedata是类的一个属性,如果您这样定义它,则不是该类的实例。

实际上,在任何时候都只有一个somedata,因为在您的示例中,您没有分配给该名称,而是使用它查找dict,然后为它分配一个项(key,value)。这是一个gotcha,它是Python解释器工作方式的结果,一开始可能会令人困惑。