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)
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")