Reputation: 752
Suppose, I have a custom metaclass and a class linked to it:
class Meta(type): pass
class A(metaclass=Meta): pass
From my understanding that at the end of the class A
statement, the following steps are executed:
Meta('A', (), {})
.type.__call__(...)
will be invoked. This is because type
is linked to the Meta.__class__
.type.__call__(...)
in turn run two other methods (a __new__
and a __init__
).Meta
defined either or both of these methods, then inside of type.__call__
these methods will be invoked as Meta.__new__(...)
and/or Meta.__init__(...)
.A
is created and linked to the Meta
(A.__class__
).Now, suppose I have a subclass of A
:
class Meta(type): pass
class A(metaclass=Meta): pass
class B(A): pass
At the end of class B
statement, are the following steps correct?
type('B', (), {})
instead of Meta
, because B.__class__
is type
.type.__call__(...)
which in turn run two other methods (__new__
and __init__
).type.__new__(type, 'B', (A,), {})
.type.__init__(cls, 'B', (A,), {})
.Suppose the above steps is correct (which I doubt), shouldn't B.__class__
give type
instead of Meta
? My reasoning is that B
is created with default type
metaclass. But print out B.__class__
gives Meta
instead of type
.
print(B.__class__) #<class '__main__.Meta'>
Also if I manually create a class with A
as parent, again the created class is linked to the Meta
.
C = type.__call__(type, 'C', (A,), {})
print(C.__class__) #<class '__main__.Meta'>
#or
D = type.__new__(type, 'D', (A,), {})
print(D.__class__) #<class '__main__.Meta'>
My question is how Python create the class B/C
and how B/C
is linked to the Meta
?
Upvotes: 5
Views: 2352
Reputation: 110726
So --- a somewhat confusing question that can be answered,and somethat simplified by simply running some examples in the interactive mode.
But to start, when you state:
type.__call__(...) in turn run two other methods (a __new__ and a __init__).
It is a simplification of what takes place.
When we create new class, like in resolving a class statement class A:
, type.__call__
is invoked alright. But is this call is searched in the class of Meta
itself. That is, the "metaclass" of "Meta" - which by default is type
.
Bear with me:
When we are talking about an ordinary class E with no custom metaclass, and you create an instance by doing E()
- Python searches for the __call__
method in the class of which E
is an instance: that is, its metaclass. As it is type, then type.__call__
is called. It is type.__call__
which calls the __new__
and __init__
methods, as you stated, but not only for metaclasses: it orchestrates these calls in any object instantiation - the exact same mechanism is used in any object instantiation in Python: both ordinary objects and classes:
In [178]: class MetaMeta(type):
...: def __call__(metacls, *args, **kw):
...: print("Now at the meta-meta class")
...: return super().__call__(*args, **kw)
...:
In [179]: class EmptyMeta(type, metaclass=MetaMeta):
...: def __call__(cls, *args, **kw):
...: print("At the metaclass __call__")
...: return super().__call__(*args, **kw)
...:
...:
...:
In [180]: class A(metaclass=EmptyMeta):
...: pass
...:
Now at the meta-meta class
In [181]: a = A()
At the metaclass __call__
In [182]: class Direct(metaclass=MetaMeta): pass
In [183]: Direct()
Now at the meta-meta class
Out[183]: <__main__.Direct at 0x7fa66bc72c10>
So, in short: when creating a class A, which is an instance of Meta, the __call__
method of the class of Meta is called. That will call __init__
and __new__
in the metaclass Meta. If those are not defined, ordinary attribute lookup will call these methods in the superclass of Meta, which happens to also be "type".
Now, moving on on your question: when one inherits from a class with a custom metaclass, like your B
class, Python takes the most derived metaclass in its superclasses as its own metaclass, not type
. No need to explicitly declare a custom metaclass. That, in practical means, is what makes metaclass needed instead of just Class decorators: these affect only the class where they are declared, and have no effect in further subclasses.
In [184]: class B(A): pass
Now at the meta-meta class
In [185]: B()
At the metaclass __call__
Out[185]: <__main__.B at 0x7fa6682ab3a0>
In [186]: B.__class__
Out[186]: __main__.EmptyMeta
Even in an explicit call to type
instead of the class
statement, the derived class' metaclass will be the metaclass of the superclass. Note, however, that in this case we are hardcoding the call to the "metameta" class to type.__new__
and the "custom metaclass of the metaclass" is ignored:
In [187]: C = type("C", (A, ), {})
In [188]: C()
At the metaclass __call__
Out[188]: <__main__.C at 0x7fa653cb0d60>
If you want to programmaticaly create a class that has a custom "meta meta class" (God forbid one needing this in anything but learning purposes), there is a special call in the types
module that does that:
In [192]: import types
In [193]: D = types.new_class("D", (A,), {})
Now at the meta-meta class
In [194]: D()
At the metaclass __call__
Out[194]: <__main__.D at 0x7fa6682959a0>
And to wrap it up, note that if the superclasses of a class have diverging metaclasses, Python will refuse to create a class at all. That is somewhat common in "real world" code, when people try to create Abstract classes (wich usea custom metaclass) with a base class in some framework with an ORM, which typically also have a custom metaclass:
In [203]: class Meta1(type): pass
In [204]: class Meta2(type): pass
In [205]: class A(metaclass=Meta1): pass
In [206]: class B(metaclass=Meta2): pass
In [207]: class C(A, B): pass
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-207-1def53cc27f4> in <module>
----> 1 class C(A, B): pass
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
Which is fixable by producing a derived metaclass that inherits from
the metaclasses in both ancestor branches (this requires that both metaclasses
are well behaved, using the super()
instead of hardcoding calls to type
- but
that is the case with well maintained and popular frameworks):
In [208]: class Meta3(Meta1, Meta2): pass
In [209]: class C(A, B, metaclass=Meta3): pass
In [210]:
Upvotes: 6
Reputation: 1141
Call type('B', (), {}) instead of Meta, because B.class is type.
As you noted later, it is not.
>>> class Meta(type): pass
...
>>> class A(metaclass=Meta): pass
...
>>> class B(A): pass
...
>>> type(B)
<class '__main__.Meta'>
>>>
My question is how Python create the class B/C and how B/C is linked to the Meta?
If X
class inherits the Y
class, then the metaclass of X
is same with the metaclass of Y
. You can find detailed information on the data model documentation.
From the docs:
The appropriate metaclass for a class definition is determined as follows:
if no bases and no explicit metaclass are given, then type() is used;
if an explicit metaclass is given and it is not an instance of type(), then it is used directly as the metaclass;
if an instance of type() is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used.
The most derived metaclass is selected from the explicitly specified metaclass (if any) and the metaclasses (i.e. type(cls)) of all specified base classes. The most derived metaclass is one which is a subtype of all of these candidate metaclasses. If none of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError.
Upvotes: 3