Metaclass Inheritance

In this well-known answer that explains the metaclass in Python. He mentions that the __metaclass__ attribute __metaclass__ not be inherited.

But actually I tried in 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 

As expected, both Foo and Bar use Meta1 as a metaclass and print string as expected.

But in the following example, when type(...) returned instead of type.__new__(...) , the metaclass is no longer inherited:

 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 

Checking the __metaclass__ and __class__ , I see:

 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'> 

Finally:

  • Both __metaclass__ and __class__ will be inherited from the base class.

  • The creation behavior defined by Meta2 will be used for Foo2 , although Foo2.__class__ is actually type .

  • The __metaclass__ attribute in Bar2 is equal to Meta2 , but the behavior of creating Bar2 not affected. In other words, Bar2 uses type as a "real" metaclass instead of Meta2 .

These observations make the __metaclass__ inheritance __metaclass__ uncertain for me.

I suppose that:

  • When you directly assign a class (for example, Meta1 ) to the __metaclass__ attribute of another class "Foo1", the __metaclass__ attribute is __metaclass__ .

  • When a subclass does not explicitly set the __metaclass__ attribute in the definition. The __class__ attribute instead of the __metaclass__ attribute of the base class will define the "real" subclass metaclass.

Did I understand correctly? How does Python handle metaclass inheritance?

+6
source share
1 answer

You speculate a lot, while Python's minimalism and “Special cases are not complex enough to break the rules.” directive, easier to understand than that.

In Python2, the __metaclass__ attribute in the cube of a class is used during class creation to call the class that will belong to this class. This is usually a class called type . To clarify, this moment after parsing the body of the class after the compiler compiled it to the code object and after it was actually launched during program execution, and only if __metaclass__ explicitly provided in this class,

Thus, to check the method, you will find yourself in a case such as:

 class A(object): __metaclass__ = MetaA class B(A): pass 

A has __metaclass__ in its body - MetaA is called instead of type to turn it into a "class object". B does not have __metaclass__ in its body. After creating it, if you just try to access the __metaclass__ attribute, it is an attribute like any other that will be visible because Python will pull it out of superclass A If you check A.__dict__ , you will see __metaclass__ , and if you check B.__dict__ , do not.

This A.__metaclass__ not used at all when creating B. If you change it to A before declaring B , it will still use the same metaclass as A , because Python uses the parent class type as a metaclass in the absence of an explicit __metaclass__ .

To illustrate:

 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'> 

Also, if you try to simply create a class via a type call instead of using the body with the class operator, __metaclass__ also a regular attribute:

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

To summarize so far . The __metaclass__ attribute in Python 2 is only special if it is explicitly placed in the declaration of the class body as part of the execution of the class block statement. This is a common attribute without special properties.

Python3 how to get rid of this strange __metaclass__ attribute __metaclass__ now not good, "and allowed further customization of the class body by changing the syntax to specify metaclasses. (This is similar to the metaclass named parameter declared in the class expression itself)

Now, in the second part of what raised your doubts: if in the __new__ method of the metaclass you call type instead of type.__new__ , there is no way that Python can "know" the type called from the derived metaclass. When you call type.__new__ , you pass the cls attribute as your first parameter, your metaclass __new__ was passed by the runtime itself: this is what places the resulting class as an instance of the type subclass. In the same way that inheritance works for any other class in Python - therefore, there are no “special actions” here:

So, indicate the difference:

 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 

This can be seen in the interactive prompt:

 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) 

(Note that the first parameter to the metaclass __new__ method is the metaclass itself, which is why metacls more correctly named than cls both in your code and in a lot of code "in the wild")

+3
source

All Articles