【问题标题】:Inheritance of metaclass元类的继承
【发布时间】:2016-09-21 11:55:37
【问题描述】:

在这个众所周知的answer 中解释了 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如何处理元类的继承?

【问题讨论】:

标签: python inheritance python-2.x metaclass


【解决方案1】:

您猜测很多,而 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 中更改它,仍将使用与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__ 时,您将cls 属性作为它的第一个参数传递,您的元类的__new__ 本身是由运行时传递的:这就是将生成的类标记为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 更恰当地命名,并且在很多“野外”代码中)

【讨论】:

    猜你喜欢
    • 2020-03-19
    • 2021-05-01
    • 2012-08-20
    • 2014-11-15
    • 2022-01-18
    • 2016-07-19
    • 2010-12-21
    • 2021-03-22
    • 1970-01-01
    相关资源
    最近更新 更多