If super
doesn't find something while looking through the method resolution order (MRO) to delegate to (or if you're looking for the attribute __class__
) it will check its own attributes.
Because object
is always the last type in the MRO (at least to my knowledge it's always the last one) you effectively disabled the delegation and it will only check the super instance.
I found the question really interesting so I went to the source code of super
and in particular the delegation part (super.__getattribute__
(in CPython 3.6.5)) and I translated it (roughly) to pure Python accompanied by some additional comments of my own:
class MySuper(object):
def __init__(self, klass, instance):
self.__thisclass__ = klass
self.__self__ = instance
self.__self_class__ = type(instance)
def __repr__(self):
# That's not in the original implementation, it's here for fun
return 'hoho'
def __getattribute__(self, name):
su_type = object.__getattribute__(self, '__thisclass__')
su_obj = object.__getattribute__(self, '__self__')
su_obj_type = object.__getattribute__(self, '__self_class__')
starttype = su_obj_type
# If asked for the __class__ don't go looking for it in the MRO!
if name == '__class__':
return object.__getattribute__(self, '__class__')
mro = starttype.mro()
n = len(mro)
# Jump ahead in the MRO to the passed in class
# excluding the last one because that is skipped anyway.
for i in range(0, n - 1):
if mro[i] is su_type:
break
# The C code only increments by one here, but the C for loop
# actually increases the i variable by one before the condition
# is checked so to get the equivalent code one needs to increment
# twice here.
i += 2
# We're at the end of the MRO. Check if super has this attribute.
if i >= n:
return object.__getattribute__(self, name)
# Go up the MRO
while True:
tmp = mro[i]
dict_ = tmp.__dict__
try:
res = dict_[name]
except:
pass
else:
# We found a match, now go through the descriptor protocol
# so that we get a bound method (or whatever is applicable)
# for this attribute.
f = type(res).__get__
f(res, None if su_obj is starttype else su_obj, starttype)
res = tmp
return res
i += 1
# Not really the nicest construct but it's a do-while loop
# in the C code and I feel like this is the closest Python
# representation of that.
if i < n:
continue
else:
break
return object.__getattribute__(self, name)
As you can see there are some ways you could end up looking up the attribute on super
:
- If you're looking for the
__class__
attribute
- If you reached the end of the MRO immediately (by passing in
object
as first argument)!
- If
__getattribute__
couldn't find a match in the remaining MRO.
Actually because it works like super
you can use that instead (at least as far as the attribute delegation is concerned):
class X(object):
def __init__(self):
print(MySuper(object, self).__repr__())
X()
That will print the hoho
from the MySuper.__repr__
. Feel free to experiment with that code by inserting some print
s to follow the control flow.
I wonder any reliable material ever mentions it and whether it applies to other Python implementations.
What I said above was based on my observations of the CPython 3.6 source, but I think it shouldn't be too different for other Python versions given that the other Python implementations (often) follow CPython.
In fact I also checked:
And all of them return the __repr__
of super
.
Note that Python follows the "We are all consenting adults" style, so I would be surprised if someone bothered to formalize such unusual usages. I mean who would try to delegate to a method of the sibling or parent class of object
(the "ultimate" parent class).