Python子类如何访问父类的类变量

42

我很惊讶地发现子类的类变量不能访问父类的类变量,除非明确指定父类的类名:

>>> class A(object):
...     x = 0
... 
>>> class B(A):
...     y = x+1
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in B
NameError: name 'x' is not defined
>>> class B(A):
...     y = A.x + 1
... 
>>> B.x
0
>>> B.y
1

为什么在定义B.y时我必须引用A.x而不是只引用x?这与实例变量的直觉相反,因为在定义B之后,我可以引用B.x。

2个回答

52

Python的裸名字(指没有被限定的变量名)的作用域规则非常简单和直观:首先在当前作用域内查找该变量,然后(如果有的话)在外围函数中查找,接着是全局作用域和内置作用域。当裸名字被查找时只会发生这些事情,无需记忆或应用任何复杂的规则(也不需要Python编译器执行更复杂的规则)。

如果您想要不同的查找方式,必须使用限定名称而不是裸名字。限定名称更加强大,因为可以委托给对象来查找其属性,并且这些对象可以实现任何所需的查找规则。特别地,在类的实例方法中,self.x 是请求 self 对象查找属性名为 'x' 的唯一方式 -- 在这种查找中它可以委托给类,包括继承概念的实现(以及多继承、方法解析顺序等)。

类的主体(与定义在类中的方法的主体不同)作为 class 语句的一部分在创建类对象或绑定类名之前执行(尤其是在定义任何基类被声明为基类之前执行 -- 但是这种最新的细节在涉及裸名字时从来不会有任何影响!)。

因此,在您的示例中,在类 B 中,裸名字 x 是按照通用规则进行查找 -- 它是否在本地作用域内被赋值?如果没有,它是否在嵌套当前作用域的任何外围函数中赋值?如果也没有,它是否在全局或内置作用域中被赋值?如果以上都不是,则使用该裸名字将引发名称错误异常。

由于您想要与普遍强制执行的裸名查找规则不同的查找序列,因此显然需要使用限定名称而不是裸名;一瞬间的反思将清楚地显示出,用于您目的的限定名称的“一个明显的选择”必须是 A.x ——因为那是您希望它被查找的位置(毕竟,在那一点上还没有记录任何基类…它将是元类,通常是 type ,在其工作得到调用后执行类体时进行基类绑定的一部分!-)。

有些人非常热衷于其他“神奇”的裸名查找规则,以至于他们无法忍受Python的这个方面(最初启发自Modula-3,一个在理论家圈子里非常受欢迎但鲜为人知的语言;-)——例如,必须在方法中编写 self.x 来指定必须在 self 上查找x而不是使用普遍的裸名规则,使这样的人精神错乱。

对我而言,我喜欢裸名查找规则的简单性和普适性,并且我喜欢在任何时候都使用限定名称而不是裸名进行任何其他形式的查找…但是,毫无秘密的是我疯狂地爱着Python(我有自己的抱怨——例如, global x 作为语句总是让我感到不舒服,而我更愿意写 global.x ,即将 global 作为“当前执行模块”的内置名称…我确实喜欢限定名称!-),不是吗?-)


5
如果您的子类存在一系列的超类,那么限定名称就需要您知道类变量在哪个超类中进行了设置。我可以看到有些情况下这种要求可以使事情更加安全,但也有一些情况下它会带来限制。似乎为类变量提供一个'super'的等效物会很不错。 - Adam Spiers
然而,在我看来,让我们有一种方法在类定义的主体中使用作为参数传递的类是相当自然的。例如:class B(super=A): y = super.x + 1。(我知道super函数的神奇力量,但我感到害怕。) - Alexey

34

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接