0

I am working through the O Reilly Python Cookbook and have been struggling with the below code. It is to with calling a method on a parent class using super():

class Proxy:
    def __init__(self, obj):
        self._obj = obj

    # Delegate attribute lookup to internal obj
    def __getattr__(self, name):
        return getattr(self._obj, name)

    # Delegate attribute assignment
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)    # Call original __setattr__
        else:
            setattr(self._obj, name, value)

if __name__ == '__main__':
    class A:
        def __init__(self, x):
            self.x = x
        def spam(self):
            print('A.spam')

    a = A(42)
    p = Proxy(a)
    print(p.x)
    print(p.spam())
    p.x = 37
    print('Should be 37:', p.x)
    print('Should be 37:', a.x)

The book states:

In this code the implementation of __setatrr__() includes a name check. If the name starts with an underscore it invokes the original implementation of __setattr__() using super(). Otherwise, it delegates to the internally held object self._obj.

I am confused. How does super() work then if there is no explicit base class listed? What exactly then is super() referring to?

martineau
  • 119,623
  • 25
  • 170
  • 301
pablowilks2
  • 299
  • 5
  • 15
  • 6
    There's *always* a base class, and the whole point of `super` is that you don't need to know which class `super()` will refer to. `Proxy` inherits directly from `object`, but that doesn't mean `Proxy` will appear immediately before `object` in the MRO of `self`. (`self` could be an instance of some class that inherits from many classes in addition to `Proxy`.) – chepner Jan 03 '20 at 22:54

1 Answers1

3

There is always a base class; with none explicitly mentioned, Proxy inherits directly from object.

Each class defines a method-resolution order, determined recursively by its base class(es) and its ancestors. When super() gets called, it resolves to a "proxy" of the next class in the MRO of self, whether or not that class appears in the MRO of the class you are currently defining.

Consider the following classes:

class A:
    def foo(self):
        print("A.foo")


class B(A):
    def foo(self):
        super().foo()
        print("B.foo")


class C(A):
    def foo(self):
        super().foo()
        print("C.foo")


class D(C):
    def foo(self):
        super().foo()
        print("D.foo")


class E(B,D):
    def foo(self):
        super().foo()
        print("E.foo")

e = E()

The MRO of E is [E, B, D, C, A, object]. When you call e.foo(), you start a chain of calls in MRO order. In particular, the call to super in B.foo does not invoke A.foo, but D.foo, a method in a class B knows nothing about, as D is not an ancestor of B. But both B and D are ancestors of E, which is what matters.

chepner
  • 497,756
  • 71
  • 530
  • 681