I'm really confused by the following code sample:
class Meta_1(type):
def __call__(cls, *a, **kw): # line 1
print("entering Meta_1.__call__()")
print(cls) # line 4
print(cls.mro()) # line 5
print(super(Meta_1, cls).__self__) # line 6
rv = super(Meta_1, cls).__call__(*a, **kw) # line 7
print("exiting Meta_1.__call__()")
return rv
class Car(object, metaclass=Meta_1):
def __new__(cls, *a, **kw):
print("Car.__new__()")
rv = super(Car, cls).__new__(cls, *a, **kw)
return rv
def __init__(self, *a, **kw):
print("Car.__init__()")
super(Car,self).__init__(*a, **kw)
if __name__ == '__main__':
c = Car()
The print message for this code is:
entering Meta_1.__call__()
<class '__main__.Car'> # line 4
[<class '__main__.Car'>, <class 'object'>] # line 5
<class '__main__.Car'> # line 6
Car.__new__()
Car.__init__()
exiting Meta_1.__call__()
The result shows that cls
of line 4 is the Car
class and its MRO list is: [<class '__main__.Car'>, <class 'object'>]
However, line 6 shows that super(Meta_1, cls).__self__
is also the Car
class.
I am really confused that:
- In line 7, It seems that
super(Meta_1, cls).__call__(*a, **kw)
eventually lead totype.__call__
. But, to my knowledge,super(arg1, arg2)
will look into the MRO of the second input argument to find the first input argument, and return the next class to it. But in line 6 and 7 of my code, the MRO for 2nd argument(Car
), does not contain the 1st input argument(Meta_1
), you cannot findMeta_1
in the MRO forCar
. so why wouldsuper(Meta_1, cos)
take us to invoketype.__call__
??
2. if super(Meta_1, cls).__self__
is the Car
class, then line 7 means it's Car
's __call__
that's being called? But calling the Car
class took us to line 1 in the first place, right? wouldn't that be a loop?
You are confusing a few concepts. The first of them is confusing the Metaclass with the class inheritance hierarchy.
Both things are ortogonal - looking at Car
's mro will show you the inheritance tree for that class, and that does not include the metaclass. In other words, no Meta_1
should not, by any means, be in the MRO (or inheritance Tree).
The metaclass is the class' type - that is, it has the templates and methods to create the class object itself. As such, it has the "mechanisms" to build the class MRO itself, and to call the class' __new__
and __init__
(and __init_subclass__
and initialize the descriptors calling their __set_name__
).
So, calling a class object, as calling any instance in Python will run the code in it's class __call__
method. In the case of a class, it happens that "calling" the class is the way to create a new instance - and what does that is the metaclass' __call__
.
The other thing you are misunderstanding there is the super()
object. Super()
is not actually the superclass, neither an instance of the superclass - it is rather a proxy object, that will relay any attribute retrieval or method call to methods and attributes on the proper superclass. As part ot the mechanism super()
uses to be able to act as a proxy, is to have the instance where it is called as its own __self__
attribute. In other words, the __self__
attribute is an ordinary attribute on the (proxy) object returned by super()
call - it is picked from the second argument, or automatically in Python 3 - and it is used internally when the super
object is used as a proxy to get act as if it were accessing attributes or methods on the "superclass" of that instance. (The instance annotated in __self__
).
When you use super()
inside the metaclass, the class proxied is the metaclass's superclass, which is type
, not Car's superclass, object
.
And so to yours second question:
- if
super(Meta_1, cls).__self__
is the Car class, then line 7 means it's Car's__call__
that's being called? But calling the Car class took us to line 1 in the first place, right? wouldn't that be a loop?
As said above, the super()
call from the metaclass' __call__
will call type.__call__
, and it will get the class Car
as its cls
parameter. That method in turn, will run Car.__new__
and Car.__init__
as the normal process to instantiate the class.
It's important to pay attention to what values are being used as each argument to super
. The primary purpose of super
is to perform attribute lookup according to some method-resolution order (MRO). The second argument determines which MRO to use; the first determines where to start looking.
An MRO is always defined by a class; when performing method resolution on an instance, we use the MRO of the class of which that instance is a type.
In the class
class Meta_1(type):
def __call__(cls, *a, **kw): # line 1
print("entering Meta_1.__call__()")
print(cls) # line 4
print(cls.mro()) # line 5
print(super(Meta_1, cls).__self__) # line 6
rv = super(Meta_1, cls).__call__(*a, **kw) # line 7
print("exiting Meta_1.__call__()")
return rv
we see two uses of super
. Both take the same arguments. cls
is some object passed as the first argument to Meta_1.__call__
. That means we'll use the MRO provided by type(cls)
, and we'll use the first class found after Meta_1
that provides the desired method. (In the first call, __self__
is an attribute of the proxy object itself, rather than an attribute or method of the class whose proxy super
returns.)
When you run your code, you see that cls
is bound to your Car
type object. That's because Car()
is implemented by type(Car).__call__()
; since Car
uses Meta_1
as its metaclass, type(Car)
is Meta_1
.
cls.mro()
is irrelevant, because that's the MRO used by instances of cls
.
The MRO of Meta_1
itself can be seen with
>>> Meta_1.mro(Meta_1)
[<class '__main__.Meta_1'>, <class 'type'>, <class 'object'>]
(mro
is an instance method of the type
class, and so requires the seemingly redundant instance of type
as an argument. Keep in mind that cls.mro()
is equivalent to type(cls).mro(cls)
.)
So line 7 is a call to type.__call__
, in order to create an instance of cls
that Meta_1.__call__
can return.
This is an excellent answer from the original post by Michael Ekoka where my sample code came from: Using the __call__ method of a metaclass instead of __new__?
Basically, I need to get a better understanding of how super()
works.
quote:
super
will indeed use cls
to find the MRO, but not the way one might think. I'm guessing you thought it would do something as direct as cls.__mro__
and find Meta_1
. Not so, that's Class_1
's MRO you're resolving by doing that, a different, unrelated MRO, and Meta_1
isn't a part of it (Class_1
does not inherit from Meta_1
). cls
even having an __mro__
property is just an accident due to it being a class. Instead, super
will look up the class (a metaclass in our case) of cls
, i.e. Meta_1
, then will look up the MRO from there (i.e. Meta_1.__mro__
).
来源:https://stackoverflow.com/questions/56691487/how-does-metaclass-work-with-the-mro-list-when-super-is-called