Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using `super()` within `__init_subclass__` doesn't find parent's classmethod [duplicate]

I try to access the classmethod of a parent from within __init_subclass__ however that doesn't seem to work. Suppose the following example code:

class Foo:
    def __init_subclass__(cls):
        print('init', cls, cls.__mro__)
        super(cls).foo()

    @classmethod
    def foo(cls):
        print('foo')


class Bar(Foo):
    pass

which produces the following exception:

AttributeError: 'super' object has no attribute 'foo'

The cls.__mro__ however shows that Foo is a part of it: (<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>).

So I don't understand why super(cls).foo() doesn't dispatch to Foo.foo. Can someone explain this?

like image 357
a_guest Avatar asked Apr 18 '18 00:04

a_guest


1 Answers

A normal super object (what you normally get from calling super(MyType, self) or super() or super(MyType, myobj)) keeps track of both the type and the object it was created with. Whenever you look up an attribute on the super, it skips over MyType in the method resolution order, but if it finds a method it binds it to that self object.

An unbound super has no self object. So, super(cls) skips over cls in the MRO to find the method foo, and then binds it to… oops, it has nothing to call it on.

So, what things can you call a classmethod on? The class itself, or a subclass of it, or an instance of that class or subclass. So, any of those will work as the second argument to super here, the most obvious one being:

super(cls, cls)

This is somewhat similar to the difference between staticmethods (bound staticmethods are actually bound to nothing) and classmethods (bound classmethods are bound to the class instead of an instance), but it's not quite that simple.


If you want to know why an unbound super doesn't work, you have to understand what an unbound super really is. Unfortunately, the only explanation in the docs is:

If the second argument is omitted, the super object returned is unbound.

What does this mean? Well, you can try to work it out from first principles as a parallel to what it means for a method to be unbound (except, of course, that unbound methods aren't a thing in modern Python), or you can read the C source, or the original introduction to 2.2's class-type unification (including a pure-Python super clone).

A super object has a __self__ attribute, just like a method object. And super(cls) is missing its __self__, just like str.split is.1

You can't use an unbound super explicitly the way you can with an unbound method (e.g., str.split('123', '2') does the same as '123'.split('2'), but super(cls).foo(cls) doesn't work the same as super(cls, cls).foo()). But you can use them implicitly, the same way you do with unbound methods all the time without normally thinking about it.

If you don't know how methods work, the tl'dr is: when you evaluate myobj.mymeth, Python looks up mymeth, doesn't find it on myobj itself, but does find it on the type, so it checks whether it's a non-data descriptor, and, if so, calls its __get__ method to bind it to myobj.

So, unbound methods2 are non-data descriptors whose __get__ method returns a bound method. Unbound @classmethods are similar, but their __get__ ignores the object and returns a bound method bound to the class. And so on.

And unbound supers are non-data descriptors whose __get__ method returns a bound super.


Example (credit to wim for coming up with the closest thing to a use for unbound super that I've seen):

class A:
    def f(self): print('A.f')
class B(A):
    def f(self): print('B.f')
b = B()
bs = super(B)
B.bs = bs
b.bs.f()

We created an unbound super bs, stuck it on the type B, and then b.bs is a normal bound super, so b.bs.f is A.f, just like super().f would have been inside a B method.

Why would you want to do that? I'm not sure. I've written all kinds of ridiculously dynamic and reflective code in Python (e.g., for transparent proxies to other interpreters), and I can't remember ever needing an unbound super. But if you ever need it, it's there.


1. I'm cheating a bit here. First, unbound methods aren't a thing anymore in Python 3—but functions work the same way, so Python uses them where it used to use unbound methods. Second, str.split, being a C builtin, wasn't properly an unbound method even in 2.x—but it acts like one anyway, at least as far as we're concerned here.

2. Actually plain-old functions.

like image 100
abarnert Avatar answered Sep 19 '22 12:09

abarnert