元类的继承

25
在这篇广为人知的Python元类相关的答案中,提到了__metaclass__属性不会被继承。
但事实上,我在Python中尝试过:
class Meta1(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta1"
        return type.__new__(cls, clsname, bases, dct)

# "Using Meta1" printed
class Foo1:
    __metaclass__ = Meta1

# "Using Meta1" printed
class Bar1(Foo1):
    pass

如预期的那样,FooBar都使用Meta1作为元类,并按预期打印字符串。

但在下面的示例中,当返回type(...)而不是type.__new__(...)时,元类不再被继承:

class Meta2(type):
    def __new__(cls, clsname, bases, dct):
        print "Using Meta2"
        return type(clsname, bases, dct)

# "Using Meta2" printed
class Foo2:
    __metaclass__ = Meta2

# Nothing printed
class Bar2(Foo2):
    pass

检查 __metaclass____class__ 属性,我可以看到:

print Foo1.__metaclass__ # <class '__main__.Meta1'>
print Bar1.__metaclass__ # <class '__main__.Meta1'>
print Foo2.__metaclass__ # <class '__main__.Meta2'>
print Bar2.__metaclass__ # <class '__main__.Meta2'>

print Foo1.__class__ # <class '__main__.Meta1'>
print Bar1.__class__ # <class '__main__.Meta1'>
print Foo2.__class__ # <type 'type'>
print Bar2.__class__ # <type 'type'>

总之:

  1. 基类中的 __metaclass____class__ 属性都将被继承。

  2. Meta2 定义的创建行为将用于 Foo2,尽管 Foo2.__class__ 实际上是 type

  3. Bar2 中的 __metaclass__ 属性是 Meta2,但不会影响 Bar2 的创建行为。换句话说,Bar2 使用 type 作为其 "真正的" 元类,而不是 Meta2

这些观察结果使我对 __metaclass__ 的继承机制有点模糊。

我的猜测是:

  1. 当直接将一个类(如 Meta1)赋值给另一个类 'Foo1' 的 __metaclass__ 属性时,它是 __metaclass__ 属性生效。

  2. 当子类在定义时没有显式设置 __metaclass__ 属性时,基类的 __class__ 属性而不是 __metaclass__ 属性将决定子类的 "真正" 元类。

我的猜测正确吗?Python 如何处理元类的继承?


我认为这与type.__new__和type有关。https://dev59.com/1HE85IYBdhLWcg3wzWvM - Vikas Madhusudana
1个回答

21

你有很多猜测,而 Python 的极简主义和“特殊情况并不足以打破规则”的指导方针使其比那更容易理解。

在 Python2 中,类体中的 __metaclass__ 属性用于在类创建时调用该类将成为的“类”。通常情况下,它是名为 type 的类。需要澄清的是,这个时刻是在解析器解析了类体之后,在编译器将其编译为代码对象之后,并且只有在该类体中明确提供了 __metaclass__ 的情况下,才会在程序运行时实际运行。

因此,让我们来看看在这种情况下会发生什么:

class A(object):
    __metaclass__ = MetaA

class B(A):
    pass

A 在其主体中具有 __metaclass__ - 为了将其转换为“类对象”,MetaA 被调用来代替 type

B 在其主体中没有 __metaclass__。在创建后,如果只是尝试访问 __metaclass__ 属性,则它是像任何其他属性一样可见的,因为 Python 将从超类 A 获取它。如果您检查 A.__dict__,则会看到 __metaclass__,但如果您检查 B.__dict__,则不会看到。

当创建 B 时,此 A.__metaclass__ 属性根本未被使用。如果在声明 B之前更改 A 中的元类,则 B 仍将使用与 A 相同的元类 - 因为在未声明明确的 __metaclass__ 的情况下,Python 使用父类的类型作为元类。

举例说明:

In [1]: class M(type): pass

In [2]: class A(object): __metaclass__ = M

In [3]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(A.__class__, A.__metaclass__, A.__dict__.get("__metaclass__"), type(A))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: <class '__main__.M'>, type: <class '__main__.M'>

In [4]: class B(A): pass

In [5]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(B.__class__, B.__metaclass__, B.__dict__.get("__metaclass__"), type(B))
class: <class '__main__.M'>, metaclass_attr: <class '__main__.M'>, metaclass_in_dict: None, type: <class '__main__.M'>

In [6]: A.__metaclass__ = type

In [8]: class C(A): pass

In [9]: print "class: {}, metaclass_attr: {}, metaclass_in_dict: {}, type: {}".format(C.__class__, C.__metaclass__, C.__dict__.get("__metaclass__"), type(C))
class: <class '__main__.M'>, metaclass_attr: <type 'type'>, metaclass_in_dict: None, type: <class '__main__.M'>

此外,如果你只是通过调用type来创建一个类,而不是使用带有class语句的主体,__metaclass__也只是一个普通属性:

In [11]: D = type("D", (object,), {"__metaclass__": M})

In [12]: type(D)
type

总结到目前为止: 在 Python 2 中,__metaclass__ 属性只有在显式放置在类体声明中作为 class 块语句的执行的一部分时才是特殊的。它是一个普通属性,在之后没有特殊属性。

Python3 现在摆脱了这种奇怪的“__metaclass__ 属性现在不好用了”,并通过更改语法以指定元类,允许进一步自定义类体。(就像在 class 语句本身上声明一个 "metaclass 命名参数" 一样)

现在,针对引起你疑虑的第二部分:如果在元类的 __new__ 方法中调用 type 而不是 type.__new__,则 Python 没有办法 "知道" type 是从派生元类中调用的。当您调用 type.__new__ 时,将其作为第一个参数传递给运行时传递的您的元类的 __new__cls 属性:这就是标记生成的类为 type 的子类的实例的内容。 这就像 Python 中任何其他类的继承工作方式一样 - 所以这里 "没有特殊行为":

所以,找出其中的区别:

class M1(type):
    def __new__(metacls, name, bases, attrs):
         cls = type.__new__(metacls, name, bases, attrs)
         # cls now is an instance of "M1"
         ...
         return cls


class M2(type):
    def __new__(metacls, name, bases, attrs):
         cls = type(name, bases, attrs)
         # Type does not "know" it was called from within "M2"
         # cls is an ordinary instance of "type"
         ...
         return cls

在交互式提示符中可以看到:

In [13]: class M2(type):
   ....:     def __new__(metacls, name, bases, attrs):
   ....:         return type(name, bases, attrs)
   ....:     

In [14]: class A(M2): pass

In [15]: type(A)
Out[15]: type

In [16]: class A(M2): __metaclass__ = M2

In [17]: A.__class__, A.__metaclass__
Out[17]: (type, __main__.M2)

(请注意,元类的__new__方法的第一个参数是元类本身,因此更适当地命名为metacls而不是您代码中的cls,以及许多“野外”代码中的cls


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