关于python:元类的继承

Inheritance of metaclass

在这个解释Python中元类的众所周知的答案中。它提到,__metaclass__属性不会被继承。

但事实上,我在python中尝试过:

1
2
3
4
5
6
7
8
9
10
11
12
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__(...)时,元类不再继承:

1
2
3
4
5
6
7
8
9
10
11
12
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__属性,可以看到:

1
2
3
4
5
6
7
8
9
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'>

综上所述:

  • __metaclass____class__都将从基类继承。

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

  • Bar2中的__metaclass__属性为Meta2属性,但不影响Bar2的创建行为。换句话说,Bar2使用type作为它的"真实"元类,而不是Meta2

  • 这些观察使我对__metaclass__的遗传机制有些模糊。

    我的猜测是:

  • 当直接将一个类(如Meta1分配给另一个类"foo1"的__metaclass__属性时,它是__metaclass__属性生效。

  • 当子类在定义时没有显式设置__metaclass__属性时。基类的__class__属性而不是__metaclass__属性将决定子类的"实"元类。

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


    你在猜测很多,而python的极简主义和"特殊情况不足以打破规则。"指令,使其更容易理解。

    在python2中,在类创建时使用类体中的__metaclass__属性来调用该类将成为的"类"。通常它是名为type的类。为了澄清,这一时刻是在解析器解析了类体之后,在编译器将其编译为代码对象之后,以及在程序运行时实际运行之后,并且仅当在该类体中显式提供__metaclass__时。

    所以让我们检查一下情况,比如:

    1
    2
    3
    4
    5
    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相同的元类,因为python在没有显式__metaclass__声明的情况下使用父类的类型作为元类。

    举例说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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__也只是一个普通属性:

    1
    2
    3
    4
    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中的任何其他类一样——所以这里的"无特殊行为"是:

    所以,找出区别:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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(cls, name, bases, attrs)
             # Type does not"know" it was called from within"M2"
             # cls is an ordinary instance of"type"
             ...
             return cls

    在交互提示中可以看到:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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__method first参数是元类本身,因此在您的代码和许多"在野外"的代码中,比cls更恰当地命名metacls)