how does metaclass work with the MRO list when super() is called?

家住魔仙堡 提交于 2019-12-07 18:34:06

问题


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:

  1. In line 7, It seems that super(Meta_1, cls).__call__(*a, **kw) eventually lead to type.__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 find Meta_1 in the MRO for Car. so why would super(Meta_1, cos) take us to invoke type.__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?


回答1:


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:

  1. 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.




回答2:


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.




回答3:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!