Calling super().method() in a subclass that does not override method - python

The Python documentation of super states:
This is useful for accessing inherited methods that have been overridden in a class.
Is there a point in calling super().method() in a subclass that does not override method?
To me there is not, since calling self.method() would be equivalent, that is to say inheritance would look method up in self's superclasses using the same type(self).__mro__ method resolution order (given by the C3 linearization of self's superclass hierarchy) than super.
So to me, super is useful in this situation:
class A:
def f(self):
print("A")
class B:
pass
class C(B, A):
def f(self):
super().f()
print("C")
C().f() # prints A C
but not in this one:
class A:
def f(self):
print("A")
class B:
pass
class C(B, A):
def g(self):
super().f() # should be just self.f()
print("C")
C().g() # prints A C

As noted by #chepner, calling super().method() in a subclass that does not override method is not equivalent to calling self.method(). The difference appears in subclasses of that subclass that override method.
Compare:
class A:
def f(self):
print("A")
class B:
pass
class C(B, A):
def g(self):
super().f() # == super(C, self).f(), so lookup starts after C in type(self).__mro__
print("C")
class D(C):
def f(self):
print("D")
D().g() # prints A C, since D.__mro__ == (D, C, B, A, object)
with:
class A:
def f(self):
print("A")
class B:
pass
class C(B, A):
def g(self):
self.f() # lookup starts at the beginning in type(self).__mro__
print("C")
class D(C):
def f(self):
print("D")
D().g() # prints D C, since D.__mro__ == (D, C, B, A, object)

Related

Calling super() without a base class

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?
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.

Call grandparent method in grand child class

Sorry, may be this is silly question, but its very confusing to me. Let's suppose we have the following classes:
class A():
def say(self):
print("A")
class B(A):
def say(self):
print("B")
class C(B):
def say(self,*args, **kwargs):
return super(C, self).say(*args, **kwargs)
I am accessing parent method in child, and it prints B, but I want to access method from class A as we are getting access from class B.
I know we can add super in class B, but I don't want to modify class B. so is there any option to get method from A directly in class C?
You can by calling A.say(self) like this:
class A():
def say(self):
print("A")
class B(A):
def say(self):
print("B")
class C(B):
def say(self):
A.say(self)
B.say(self)
print("C")
Then to test it out from a terminal:
>>> a = A()
>>> a.say()
A
>>> b = B()
>>> b.say()
B
>>> c = C()
>>> c.say()
A
B
C
Note: I dropped the args and kwargs because the A and B classes didn't use those arguments. If you wanted to make say take those all the way up though simply call A.say(self, *args, **kwargs) and if A.say returns something you can return it too

Python Inheritence Subclass(ing)

Given a parent class 'A'
class A(object):
def __init__(self,a,b):
self.a = a
self.b = b
def methodA():
# do something
What is the difference between making a subclass 'B' among the below options
Option 1
class B(A):
def methodB():
# do something
Option 2
class B(A):
def __init__(self,a,b):
A.__init__(self, a, b)
def methodB():
# do something
class A(object):
def __init__(self,a,b):
self.a = a
self.b = b
class B(A):
def __init__(self,a,b):
A.__init__(self, a, b)
def methodB():
pass
class C(A):
def methodB():
pass
b = B(1,2)
c = C(1,2)
print b.a == c.a # True
print b.b == c.b # True
In both class instantiation, init under class A will be ran only once.
so no, there is nothing significantly different.
class B is not clean IMO and poses no real purpose at all. It will be ran anyways.
If you wish to do something different in class B init, then yes, you can use this code.
class B(A):
def __init__(self,a,b):
A.__init__(self, a+1, b+1)
def methodB():
pass

Any difference in adding grandparent into MRO in python?

Are there any side-effects of adding grandparent to the MRO? Purpose is I would like to get grandchildren classes by using grandparent.__subclasses__()
class A(object):
def work(self):
print 'A'
class B(A):
def work(self):
print 'B'
super(B, self).work()
class C(B):
def work(self):
print 'C'
super(C, self).work()
class D(B, A):
def work(self):
print 'D'
super(D, self).work()
>>> C().work()
C
B
A
>>> D().work()
D
B
A
>>> A.__subclasses__()
[<class 'test.B'>, <class 'test.D'>]
We have come up a recursive function to work out the subclasses
def get_all_subclasses(cls):
all_subclasses = []
for subclass in cls.__subclasses__():
all_subclasses.append(subclass)
all_subclasses.extend(get_all_subclasses(subclass))
return all_subclasses

Regarding python MRO and how super() behaves

Today i was trying to figure out how __mro__ and super works in python, I found something interesting as well as strange to me, because I got something which is not that i understood after reading __mro__. Here is the code snippets.
Code Snippets 1:
#!/usr/bin/pyhon
class A(object):
def test(self):
return 'A'
class B(A):
def test(self):
return 'B to %s' % super(B, self).test()
class C(A):
def test(self):
return 'C'
class D(B, C):
pass
print D().test()
Output :
B to C
Code snippet 2: When I update my super inside class B:
#!/usr/bin/pyhon
class A(object):
def test(self):
return 'A'
class B(A):
def test(self):
return 'B to %s' % super(C, self).test()
class C(A):
def test(self):
return 'C'
class D(B, C):
pass
print D().test()
Output:
B to A
Then now i got what I expected before. Could someone please explain how mro works with super ?
The MRO for D is [D, B, C, A, object].
super(C, self) ~ A
super(B, self) ~ C
super(MyClass, self) is not about the "base class of MyClass", but about the next class in the MRO list of MyClass.
As stated in the comments, super(…) actually does not return the next class in the MRO, but delegates calls to it.

Categories