Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get the instance method's next-in-line parent class from `super()` in Python

I'd like to know the type of an instance obtained from super() function. I tried print(super()) and __print(type(super()))__

class Base:
    def __init__(self):
        pass

class Derive(Base):
    def __init__(self):
        print(super())        
        print(type(super()))        
        super().__init__()

d = Derive()

The result is

<super: <class 'Derive'>, <Derive object>>
<class 'super'>

With those result, I was wondering how super().__init__() calls the correct constructor.

like image 564
wannik Avatar asked Aug 14 '15 16:08

wannik


2 Answers

You can't do what you want with super() directly. Go to the class MRO (see class.__mro__) instead:

class Derive(Base):
    def __init__(self):
        mro = type(self).__mro__
        parent = mro[mro.index(__class__) + 1]
        print(parent)

Here __class__ is the magic closure variable* that references the class the current function was defined in; the above continues to work even when you subclass or mix in additional classes with Derive, even when you produce a diamond inheritance pattern.

Demo:

>>> class Base: pass
... 
>>> class Derive(Base):
...     def __init__(self):
...         mro = type(self).__mro__
...         parent = mro[mro.index(__class__) + 1]
...         print(parent)
... 
>>> Derive()
<class '__main__.Base'>
<__main__.Derive object at 0x10f7476a0>
>>> class Mixin(Base): pass
... 
>>> class Multiple(Derive, Mixin): pass
... 
>>> Multiple()
<class '__main__.Mixin'>
<__main__.Multiple object at 0x10f747ba8>

Note how the Multiple class inherits from both Derive and Mixin, and the next class in the MRO is thus found to be Mixin, not Base, because Mixin also derives from Base.

This copies what super() does; find the next class in the MRO for the instance, relative to the current class.


* For background, see Why is Python 3.x's super() magic?

like image 198
Martijn Pieters Avatar answered Oct 26 '22 23:10

Martijn Pieters


From your comments, you want to know how super knows which method to call next. Super inspects the mro of the instance, knows the current class method it's in, and calls the next one in line. The following demo will work in Python 2 and 3, and in Python 2, it prints the name of each class thanks to the metaclass, so I'll use that output:

First the imports and setup to make the printing nicer:

import inspect

class Meta(type):
    def __repr__(cls):
        return cls.__name__

Next, we define a function to tell us what's going on based on the super object itself

def next_in_line(supobj):
    print('The instance class: {}'.format(supobj.__self_class__))
    print('in this class\'s method: {}'.format(supobj.__thisclass__))
    mro = inspect.getmro(supobj.__self_class__)
    nextindex = mro.index(supobj.__thisclass__) + 1
    print('super will go to {} next'.format(mro[nextindex]))

Finally, we declare a class hierarchy based on the example from the wikipedia entry on C3 linearization, for a sufficiently complex example, note the metaclass repr doesn't work in Python3, but the attribute assignment won't break it. Also note that we use the full super call of super(Name, self) which is equivalent to super() in Python 3, and will still work:

class O(object):
    __metaclass__ = Meta

    def __init__(self):
        next_in_line(super(O, self))
        super(O, self).__init__()

class A(O):
    def __init__(self):
        next_in_line(super(A, self))
        super(A, self).__init__()


class B(O):
    def __init__(self):
        next_in_line(super(B, self))
        super(B, self).__init__()


class C(O):
    def __init__(self):
        next_in_line(super(C, self))
        super(C, self).__init__()


class D(O):
    def __init__(self):
        next_in_line(super(D, self))
        super(D, self).__init__()


class E(O):
    def __init__(self):
        next_in_line(super(E, self))
        super(E, self).__init__()


class K1(A, B, C):
    def __init__(self):
        next_in_line(super(K1, self))
        super(K1, self).__init__()


class K2(D, B, E):
    def __init__(self):
        next_in_line(super(K2, self))
        super(K2, self).__init__()


class K3(D, A):
    def __init__(self):
        next_in_line(super(K3, self))
        super(K3, self).__init__()


class Z(K1, K2, K3):
    def __init__(self):
        next_in_line(super(Z, self))
        super(Z, self).__init__()

Now when we print the mro of Z, we get the method resolution order defined by this algorithm applied to the inheritance tree:

>>> print(inspect.getmro(Z))
(Z, K1, K2, K3, D, A, B, C, E, O, <type 'object'>)

And when we call Z(), because our function uses the mro, we'll visit each method in order:

>>> Z()
The instance class: Z
in this class's method: Z
super will go to K1 next
The instance class: Z
in this class's method: K1
super will go to K2 next
The instance class: Z
in this class's method: K2
super will go to K3 next
The instance class: Z
in this class's method: K3
super will go to D next
The instance class: Z
in this class's method: D
super will go to A next
The instance class: Z
in this class's method: A
super will go to B next
The instance class: Z
in this class's method: B
super will go to C next
The instance class: Z
in this class's method: C
super will go to E next
The instance class: Z
in this class's method: E
super will go to O next
The instance class: Z
in this class's method: O
super will go to <type 'object'> next

And we stop at object.__init__. From the above we can see that super always knows what class of the instance it is in, the class's method that it is currently in, and can deduce from the instance class's MRO where to go next.


I'd like to know the name of the base class?

If you only want the direct base (or more than one, in the case of multiple inheritance), you can use the __bases__ attribute, which returns a tuple

>>> Derive.__bases__
(<class __main__.Base at 0xffeb517c>,)

>>> Derive.__bases__[0].__name__
'Base'

I recommend the inspect module for getting the Method Resolution Order (which super follows based on the original caller's class):

>>> import inspect
>>> inspect.getmro(Derive)
(<class __main__.Derive at 0xffeb51dc>, <class __main__.Base at 0xffeb517c>)

Getting it from super

super().__self_class__ gives the instance class, and super().__thisclass__ gives us the current class. We can use the instance's MRO and look up the class that comes next. I presume you wouldn't do this in the final parent, so I'm not catching an index error:

class Base:
    def __init__(self):
        print(super().__self_class__)
        print(super().__thisclass__)


class Derive(Base):
    def __init__(self):
        print(super().__self_class__)
        print(super().__thisclass__)
        mro = inspect.getmro(super().__self_class__)
        nextindex = mro.index(super().__thisclass__) + 1
        print('super will go to {} next'.format(mro[nextindex]))
        super().__init__()


>>> d = Derive()
<class '__main__.Derive'>
<class '__main__.Derive'>
super will go to <class '__main__.Base'> next
<class '__main__.Derive'>
<class '__main__.Base'>
like image 33
Russia Must Remove Putin Avatar answered Oct 27 '22 00:10

Russia Must Remove Putin