why is there difference between calling super().foo and super().__getattribute__("foo")

Viewed 104

I thought super() would always try to run with method(or attribute) in base class until I encountered a use case that gets attribute of child class with super().__getattribute__("foo"). Simplified code shows as below.

class Base:
    value = "base"

    def foo(self):
        print(f"Base foo, {self.value}")

class Derived(Base):
    value = "derived"

    def foo(self):
        print(f"Derived foo, {self.value}")


    def bar(self):
        print(f"Derived bar {self.value}")
        print(super().value)
        print(super().__getattribute__("value"))
        super().foo()
        super().__getattribute__('foo')()

d = Derived()
d.bar()

the output,

Derived bar derived
base
derived
Base foo, derived
Derived foo, derived

is somewhat beyond my previous understanding, is only __getattribute__ an exception? I can not get any further detail with this doc, hope anyone can help me understand this clearer, thanks!


Edit to follow __getattribute__ issue, as below:

class Base:
    value = "base"

    def foo(self):
        print(f"Base foo, {self.value}")


    def __getattribute__(self, k):
        print(f"Base get attr {self}, {k}")
        return super().__getattribute__(k)


class Derived(Base):
    value = "derived"

    def foo(self):
        print(f"Derived foo, {self.value}")


    def __getattribute__(self, k):
        print(f"Derived get attr {self}, {k}")
        return super().__getattribute__(k)


    def bar(self):
        print("Derived bar")
        print(super().value)
        print(super().__getattribute__("value"))

d = Derived()
d.bar()

and output is:

Derived get attr <__main__.Derived object at 0x7fb0621dba90>, bar
Base get attr <__main__.Derived object at 0x7fb0621dba90>, bar
Derived bar
base
Base get attr <__main__.Derived object at 0x7fb0621dba90>, value
derived
2 Answers

The super() helper creates a proxy which modifies attribute access on itself. As such:

  • super().foo looks up foo on the super proxy.
  • super().__getattribute__ looks up __getattribute__ on the super proxy.

Notably, the result of either has no knowledge that it was looked up via super: any further action on the result proceeds as normal. When the looked up thing is a data descriptor (like a method or property), it is bound to the initial self, not super.

Ultimately, looking up super().__getattribute__ will in the usual case merely find, directly or via a wrapper, the standard object.__getattribute__ and bind it to self. As such, calling super().__getattribute__("foo") is equivalent to object.__getattribute__(self, "foo") – there is no super involved in looking up .foo.


It might help to compare how the expressions super().foo and super().__getattribute__("foo") can be decomposed:

# super().foo
s = super()
foo = s.foo
# super().__getattribute__("foo")
s = super()
g = s.__getattribute__
foo = g("foo")

In the second case, the super proxy is not involved in looking up .foo.

To be more specific, super essentially skips the current class when looking up some attribute/method. In the case of single inheritance, that is equivalent to looking in the base class. This is always happening in the above.

In [38]: class Base:
    ...:     value = "base"
    ...:
    ...:     def foo(self):
    ...:         print(f"Base foo, {self.value}")
    ...:
    ...: class Derived(Base):
    ...:     value = "derived"
    ...:
    ...:     def foo(self):
    ...:         print(f"Derived foo, {self.value}")
    ...:
    ...:
    ...:     def bar(self):
    ...:         print(f"Derived bar {self.value}")
    ...:         print(super().value)
    ...:         print(super().__getattribute__("value"))
    ...:         super().foo()
    ...:         super().__getattribute__('foo')()
    ...:
    ...: d = Derived()

In [39]: Derived.mro()
Out[39]: [__main__.Derived, __main__.Base, object]

I suspect what is confusing you is:

super().__getattribute__("value")

Well, in this case, super() skipps __main__.Derived, looks in __main__.Base, doesn't find anything, then finally, finds __getattribute__ in object. It doesn't then magically make every other attribute access skip Derived. Indeed, object.__getattribute__ is what would happen anyway, Note:

In [40]: object.__getattribute__(d, 'value')
Out[40]: 'derived'

So, this works no differently when you pass "foo" to object.__getattribute__,

In [41]: object.__getattribute__(d, 'foo')
Out[41]: <bound method Derived.foo of <__main__.Derived object at 0x7f927ba46460>>

Now, when you call super().foo, it skips __main__.Derived, looks in __main__.Base and finds a foo, so that is essentially:

In [42]: Base.foo(d)
Base foo, derived
Related