Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is str(super(B, b)) not equivalent to super(B, b).__str__()?

  • 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 as super().__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 as super()[name].

like image 243
tjanson Avatar asked Sep 10 '25 20:09

tjanson


1 Answers

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.

like image 82
MSeifert Avatar answered Sep 12 '25 11:09

MSeifert