Assume A
is the parent class of B
and b
is an instance of B
. Then an overriden method of A
can be called with super: super(B, b).method()
.
The docs state "str(object)
returns object.__str__()
" in its basic invocation.
It should follow that str(super(B, b)) == super(B, b).__str__()
, but that's not the case (interactive version):
class A:
def __str__(self):
return "A"
class B(A):
def __str__(self):
return "B"
b = B()
b_super = super(B, b)
print(str(b_super)) # "<super: <class 'B'>, <B object>>"
print(b_super.__str__()) # "A"
So where did I go wrong? Does the super mechanism not work for magic methods? Does str
not invoke __str__
in this case? Is it related to this paragraph:
Note that
super()
is implemented as part of the binding process for explicit dotted attribute lookups such assuper().__getitem__(name)
. It does so by implementing its own__getattribute__()
method for searching classes in a predictable order that supports cooperative multiple inheritance. Accordingly,super()
is undefined for implicit lookups using statements or operators such assuper()[name]
.
str()
doesn't look up the __str__
method through the normal attribute lookup procedure. Instead, it performs a direct search for the __str__
method in the __dict__
s of its argument's class hierarchy, in MRO order. This finds super.__str__
, which gives "<super: <class 'B'>, <B object>>"
.
However, when you look up b_super.__str__
manually, that goes through super.__getattribute__
, the hook super
uses to provide its special attribute lookup behavior. The lookup through __getattribute__
will resolve to A.__str__
and call that.
Consider this class, which illustrates the difference (I hope):
class B(object):
def __init__(self, other):
self.other = other
def __getattribute__(self, name):
if name == 'other':
return object.__getattribute__(self, 'other')
elif name == '__str__':
return getattr(self.other, name)
else:
return name
def __str__(self):
return 'fun'
>>> str(B(1)) # calls B.__str__ because it doesn't invoke __getattribute__
'fun'
>>> B(1).__str__() # calls B.__getattribute__ to look up the __str__ method which returns (1).__str__
'1'
The problem in this case and likewise for super
is that these are proxies that rely on __getattribute__
to forward it. So any function or method that doesn't go through __getattribute__
doesn't forward. And str()
is such a function.
Just for completeness because it was mentioned in the comments and the other answer.
But str(x)
isn't equivalent to type(x).__str__(x)
because str()
even avoids the normal attribute lookup procedure of the "function on the class". It only checks the tp_str
(or if that's NULL the tp_repr
) slot of the class. So it doesn't even invoke __getattribute__
of the metaclass, which type(x).__str__(x)
would do:
class A(type):
def __getattribute__(self, name):
print(name)
if name == '__str__':
return lambda self: 'A'
else:
return type.__getattribute__(self, name)
class B(metaclass=A):
def __str__(self):
return 'B'
>>> b = B()
>>> str(b)
'B'
>>> type(b).__str__(b)
__str__
'A'
However in the absense of a metaclass it might be helpful to think of str(x)
as equivalent to type(x).__str__(x)
. But while (potentially) helpful it's not correct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With