2

例子:

class A:
    a = 1

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

z = B()
z.a # works fine
B.a # works fine

为什么x = a不允许?在所有其他上下文中(通过实例访问,通过子类名称访问)它都可以正常工作;但不知何故在类本身内部,它不起作用。

鉴于这种行为,我似乎无法实现一个类层次结构,其中每个类都定义了一些额外的属性——因为如果不知道它们在层次结构中的确切定义位置,我将无法在子类中访问它们。

这是我试图(不成功)做的事情:

class X:
  accelerate = compose(f1, f2, f3) # f1, f2, f3 are functions

class Y(X):
  move = compose(f4, f5)
  stop = f6

class Z(Y):
  action = compose(accelerate, stop)

class U(Y):
  action = compose(move, stop)

这些类根本不会被初始化;我只是想用它们来创建函数层次结构。

4

3 回答 3

7

当你写这个:

class B(A):
    b = 2
    y = b # works fine
    x = a # NameError: name 'a' is not defined
    x = A.a # works fine

Python 所做的是创建一个新范围(存储在字典中),执行所有定义,然后在类块的末尾将字典传递给type类(除非您设置了另一个元类)以创建新类。它大致相当于:

B = type('B', (A,), classdict_from_scope)

在你到达那个阶段之前,这个类B不存在,更不用说有任何基类来继承属性了。考虑到您可能有多个基类,并且在这些类中查找名称的解析顺序很复杂,并且取决于它们的完整集及其所有基类;B在实际创建您的子类之前,不会确定此顺序。

这意味着当 Python 开始执行时x = a,它发现a不在范围内,并且它没有对类或实例进行属性查找,因此它不能遵循名称解析协议来寻找替代绑定。所以它所能做的就是抛出一个错误。

就是Python 以这种方式工作的原因。你能为这个做什么?

你有两个基本的选择。

  1. 您可以明确指定要在其中查找属性的类。
  2. 创建子类后,您可以在子类中查找属性。

请注意,如果您只使用单继承,则 (1) 并没有那么糟糕;A您可以在;中查找所有属性 如果它们仅在Athis 的父类中定义,仍然会找到它们,因此您实际上不需要知道它们在层次结构中的定义位置。

如果您有多重继承,那么您至少必须知道哪个层次结构包含您要查找的属性。如果这很难或不可能,那么您可以使用选项 (2),它看起来像这样:

class B(A):
    b = 2
    y = b

B.x = B.a

这看起来有点难看,但构造了一个与在类块中B创建的类相同的类,并且如果将赋值直接放在类块之后,则任何其他代码都不会看到没有x的瞬态类。(不过,如果您使用类装饰器,它可能不会有相同的结果)BxB.x

于 2012-01-25T06:48:35.987 回答
2

类成员定义不必以类名作为前缀(因为它们在class块内),但accesses可以(因为不清楚您要访问什么)。

您可以访问b,因为它已经在该代码块的本地范围内(它在那里定义)。a不是; 它仅在类完全定义后出现。

为什么需要这样做x = a?为什么要将一个成员变量的值基于另一个成员变量?如果你真的想要,你可以使用__init__类的函数来复制值(因为子类是在__init__运行时完全定义的)。

于 2012-01-25T06:18:51.873 回答
1

本的回答很好地解释了发生了什么。

但是,由于您使用的是 Python 3,因此 Python 的元类中添加了一项功能,可以让您完成想要做的事情 - 可以更新类的本地字典(以非 hacky 方式,例如显式调用“locals ()") 在类体被解析之前。

所需要的只是使用__prepare__元类上的方法——它被传递类名,它的基类作为一个元组,并期望返回将用于解析主体类的字典对象:

class MetaCompose(type):
    @staticmethod
    def __prepare__(name, bases):
        dct = {}
        for base in reversed(bases):
            dct.update(base.__dict__)
        return dct


class A:
    a = 1

class B(A, metaclass=MetaCompose):
    x = a


B.x

(参见http://docs.python.org/py3k/reference/datamodel.html#customizing-class-creation

于 2012-01-25T11:12:05.030 回答