为什么属性引用在Python继承中会这样表现?

22 投票
3 回答
8769 浏览
提问于 2025-04-11 09:32

下面的情况看起来有点奇怪……基本上,所有从 the_base_class 继承的类似乎都共享了 somedata 这个属性。

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 显然是解决这个问题的正确方法(这样每个类就有自己的 somedata 字典)——但是这种行为在什么情况下是有用的呢?

3 个回答

3

我觉得理解这个问题最简单的方法是要明白,somedata 是类的一个属性,而不是这个类的一个实例的属性,如果你是这样定义的话。

实际上,somedata 在任何时候只有一个,因为在你的例子中,你并没有给这个名字赋值,而是用它去查找一个字典,然后把一个项目(键,值)赋给它。这是因为 Python 解释器的工作方式造成的一个小陷阱,刚开始可能会让人感到困惑。

12

请注意,你看到的部分行为是因为 somedata 是一个 dict(字典),而不是像 bool(布尔值)这样的简单数据类型。

比如,看看这个不同的例子,它的表现方式不同(虽然很相似):

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 引用的字典对象(还有其他子类实例引用的)是被修改的。

想要更深入的理解,可以看看 Torsten Marek 对这个答案的评论。

24

你说得对,somedata 是在这个类和它的子类之间共享的,因为它是在类定义的时候就创建的。下面这几行

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

是在类被定义的时候执行的,也就是说,当解释器遇到 class 这个语句时执行——而不是在实例被创建的时候(可以想象成Java中的静态初始化块)。如果某个属性在类的实例中不存在,就会去检查这个类对象本身有没有这个属性。

在类定义的时候,你可以运行任意的代码,比如这样:

 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,而在其他系统上则会打印出其他的字符串。

相比之下,__init__ 中创建的对象是在实例化的时候创建的,只属于这个实例(当它们被赋值给 self 时):

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

在类对象上定义的对象而不是在实例上定义的对象在很多情况下是有用的。例如,你可能想要缓存你的类的实例,这样具有相同成员值的实例就可以共享(假设它们是不可变的):

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))

通常,我在类体中使用数据时会结合元类或通用工厂方法。

撰写回答