In 'Doc/reference/datamodel.rst', the 'Invoking Descriptors' documentation specifies the following behavior for super objects:
[snip]
Super Binding
If ``a`` is an instance of :class:`super`, then the binding ``super(B,
obj).m()`` searches ``obj.__class__.__mro__`` for the base class ``A``
immediately preceding ``B`` and then invokes the descriptor with the call:
``A.__dict__['m'].__get__(obj, A)``.
[snip]
In the above paragrah, the call:
A.__dict__['m'].__get__(obj, A)
is incorrect. In reality, the descriptor is invoked via:
A.__dict__['m'].__get__(obj, obj.__class__)
Loosely speaking, the 'owner' argument is set to the subclass associated with the super object, not the superclass. There is a similar error in the 'Descriptor HowTo Guide' under 'Invoking Descriptors'
(Strictly speaking, the specification is inaccurate on some other points. It assumes obj is not a class and doesn't state that the entire mro preceding 'B' is searched for 'm'. But those may be considered simplifications for the sake of brevity)
I considered reporting this as a bug in the implementation rather than the specification. But I think the implementation's algorithm is the correct one. In particular, it yields the desired behavior for classmethods In any case, the current behavior has been around for a while (it was the fix for issue #535444, dating back to 2.2)
Code demonstrating the current behavior:
class Desc(object):
def __get__(self, instance, owner):
return owner
class A(object):
attr = Desc()
class B(A):
pass
class C(B):
pass
instance = C()
assert super(B,instance).attr == C
assert super(B,C).attr == C
#According to the specification, the assertions should be:
#assert super(B,instance).attr == A
#assert super(B,C).attr == A
|