为什么属性引用在Python继承中会这样表现?
下面的情况看起来有点奇怪……基本上,所有从 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 个回答
我觉得理解这个问题最简单的方法是要明白,somedata
是类的一个属性,而不是这个类的一个实例的属性,如果你是这样定义的话。
实际上,somedata
在任何时候只有一个,因为在你的例子中,你并没有给这个名字赋值,而是用它去查找一个字典,然后把一个项目(键,值)赋给它。这是因为 Python 解释器的工作方式造成的一个小陷阱,刚开始可能会让人感到困惑。
请注意,你看到的部分行为是因为 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 对这个答案的评论。
你说得对,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))
通常,我在类体中使用数据时会结合元类或通用工厂方法。