I have trouble with understanding what happens when overriding a function that is used within another function in python class inheritance. Now say I have two classes A and B, with B as subclass, with the folliwing definitions:
class A:
def __init__(self):
self.var = 1
def print(self):
self._print()
def _print(self):
print(self.var)
class B(A):
def __init__(self):
self.var = 1
def _print(self):
print(self.var * 2)
now say:
aa = A()
bb = B()
Now, in the above code, _print() is overridden in B. As I expect, calling bb.print() prints 2, as does calling bb.print(). However, executing super(B, bb).print() also prints 2, and only executing super(B, bb)._print() prints 1. This is what I do not fully understand. I expected to have super(B, bb).print() printing 1 as I call a method of the superclass.
Could someone elaborate on this behaviour?
Related
We have two basic class.
class A:
def __init__(self) -> None:
pass
class B(A):
def __init__(self) -> None:
print(self)
A.__init__(self)
When initializing B, when can see that the "self" being passed to A is an instance of B object.
a = A()
b = B()
>> <__main__.B object at 0x0000021CC0E39700>
Now, if we print b, we can see that it is also an instance of an instance of a B object. However, A cannot be initialized with an instance of B outside of B.
print(b)
A(b)
>> TypeError: __init__() takes 1 positional argument but 2 were given
And a here will be None:
b = B()
a = A.__init__(b)
How can that be done?
I think it can not be done.
The point here is, that upon calling the constructor of a class, python will create a new instance of that class in memory and then will hand this new object plus all the additional arguments to __init__(self, *args, **kwargs).
So by calling A(b) you are actually calling an __init__-method, that has one additional parameter, i.e. __init__(self, b).
You can use super to do this
class A:
def __init__(self):
print("__init__ from A")
self.a = 1
class B(A):
def __init__(self):
print("__init__ from B")
self.b = 2
b = B()
super(type(b), b).__init__()
print(b.a)
Output
__init__ from B
__init__ from A
1
Ideally you should handle all the parent class method calls inside the derived class methods.
This question already has answers here:
When calling super() in a derived class, can I pass in self.__class__? [duplicate]
(2 answers)
Closed 9 years ago.
Here is the code I was trying to write:
class A(object):
def bind_foo(self):
old_foo = self.foo
def new_foo():
old_foo()
#super().foo()
super(self.__class__,self).foo()
self.foo = new_foo
def __init__(self):
print("A __init__")
def foo(self):
print("A foo")
class B(A):
def __init__(self):
print("B __init__")
super().__init__()
def foo(self):
print("B foo")
super().foo()
class C(A):
def __init__(self):
print("C __init__")
super().__init__()
super().bind_foo()
def foo(self):
print("C foo")
b = B()
b.foo()
c = C()
c.foo()
Class B and A is the expected behavior, that is, when I call b.foo(), it calls a.foo() as well with super(). Class C is the trying to mimic the child B and parent A behavior but this time I dont want to put explicitly super().foo() in the child class but I still want the parent foo() to be called. It works as expected.
However, what I dont quite get is that, under A.bind_foo, I have to use super(self.__class__,self).foo() rather than super().foo. super().foo gives a
"SystemError: super(): no arguments".
Can someone explain why that is so?
You should not use self.__class__ or type(self) when calling super().
In Python 3, a call to super() without arguments is equivalent to super(B, self) (within methods on class B); note the explicit naming of the class. The Python compiler adds a __class__ closure cell to methods that use super() without arguments (see Why is Python 3.x's super() magic?) that references the current class being defined.
If you use super(self.__class__, self) or super(type(self), self), you will hit an infinite recursion exception when a subclass tries to call that method; at that time self.__class__ is the derived class, not the original. See When calling super() in a derived class, can I pass in self.__class__?
So, to summarize, in Python 3:
class B(A):
def __init__(self):
print("B __init__")
super().__init__()
def foo(self):
print("B foo")
super().foo()
is equal to:
class B(A):
def __init__(self):
print("B __init__")
super(B, self).__init__()
def foo(self):
print("B foo")
super(B, self).foo()
but you should use the former, as it saves you repeating yourself.
In Python 2, you are stuck with the second form only.
For your bind_foo() method, you'll have to pass in an explicit class from which to search the MRO from, as the Python compiler cannot determine here what class is used when you bind the new replacement foo:
def bind_foo(self, klass=None):
old_foo = self.foo
if klass is None:
klass = type(self)
def new_foo():
old_foo()
super(klass, self).foo()
self.foo = new_foo
You could use __class__ (no self) to have Python provide you with the closure cell, but that'd be a reference to A, not C here. When you are binding the new foo, you want the search for overridden methods in the MRO to start searching at C instead.
Note that if you now create a class D, subclassing from C, things will go wrong again, because now you are calling bind_foo() and in turn call super() with D, not C, as the starting point. Your best bet then is to call bind_foo() with an explicit class reference. Here __class__ (no self.) will do nicely:
class C(A):
def __init__(self):
print("C __init__")
super().__init__()
self.bind_foo(__class__)
Now you have the same behaviour as using super() without arguments, a reference to the current class, the one in which you are defining the method __init__, is passed to super(), making the new_foo() behave as if it was defined directly in the class definition of C.
Note that there is no point in calling bind_foo() on super() here; you didn't override it here, so you can just call self.bind_foo() instead.
I would expect the following code to print 012345 but it prints 012012. Why? I would expect the calls to incr to be accessing the same variables since they are inherited from the same class but they are clearly different variables.
class a(object):
var = 0
#classmethod
def incr(cls):
print cls.var
cls.var+=1
class b(a):
def func(self):
super(b,self).incr()
class c(a):
def func(self):
super(c,self).incr()
t = a()
t1 = b()
t2 = c()
t1.func()
t1.func()
t1.func()
t2.func()
t2.func()
t2.func()
They are inherited from the same class, but the cls passed to the classmethod via super is the current class where the method was called from. super accesses the base class version of the method, but the cls for the call is the class where the super call was made.
This is one of the subtle differences between doing:
def func(self):
super(c, self).incr() # same as a.__dict__['incr'].__get__(self, type(self))()
and:
def func(self):
a.incr()
You can confirm this by printing the current cls in your incr method in both cases:
def incr(cls):
print cls
...
You should never assume that all super does is make a method call bound to the parent class. It does a lot more.
Keep in mind that when the first augmented assignment += is performed, the initial value of var is read from the base class (since at this point it does not exist in the dict of the subclasses). The updated value is however written to the subclass. Calling super from the second subclass repeats the same behavior of reading the initial var value from a.
There is a way to produce the sequence 012345. You have to make sure that the var of class a is increased in the incr method, even when it is called in the subclasses.
To achieve this, increment by a.var += 1, not by cls.var += 1.
As pointed out by the other answers, the var is also inherited to b and c.
By using cls.var += 1 both subclasses increase their own var instead of a's var.
class a:
var = 0
#classmethod
def incr(cls):
print(cls.var)
a.var += 1
class b(a):
def f(self):
super().incr()
class c(a):
def f(self):
super().incr()
cb = b()
cc = c()
cb.incr()
cb.incr()
cb.incr()
cc.incr()
cc.incr()
cc.incr()
cb.incr()
cc.incr()
Produces:
0
1
2
3
4
5
6
7
Both class b and class c inherit from class a separately, and var is set to 0 each time.
One way to have class c to get the same value of var in class a as class b does, class c can inherit from class b like so:
class a(object):
var = 0
#classmethod
def incr(cls):
print cls.var
cls.var+=1
class b(a):
def func(self):
super(b,self).incr()
class c(b):
def func(self):
super(c,self).incr()
t = a()
t1 = b()
t2 = c()
t1.func()
t1.func()
t1.func()
t2.func()
t2.func()
t2.func()`
I would expect the following code to print 012345 but it prints 012012. Why? I would expect the calls to incr to be accessing the same variables since they are inherited from the same class but they are clearly different variables.
class a(object):
var = 0
#classmethod
def incr(cls):
print cls.var
cls.var+=1
class b(a):
def func(self):
super(b,self).incr()
class c(a):
def func(self):
super(c,self).incr()
t = a()
t1 = b()
t2 = c()
t1.func()
t1.func()
t1.func()
t2.func()
t2.func()
t2.func()
They are inherited from the same class, but the cls passed to the classmethod via super is the current class where the method was called from. super accesses the base class version of the method, but the cls for the call is the class where the super call was made.
This is one of the subtle differences between doing:
def func(self):
super(c, self).incr() # same as a.__dict__['incr'].__get__(self, type(self))()
and:
def func(self):
a.incr()
You can confirm this by printing the current cls in your incr method in both cases:
def incr(cls):
print cls
...
You should never assume that all super does is make a method call bound to the parent class. It does a lot more.
Keep in mind that when the first augmented assignment += is performed, the initial value of var is read from the base class (since at this point it does not exist in the dict of the subclasses). The updated value is however written to the subclass. Calling super from the second subclass repeats the same behavior of reading the initial var value from a.
There is a way to produce the sequence 012345. You have to make sure that the var of class a is increased in the incr method, even when it is called in the subclasses.
To achieve this, increment by a.var += 1, not by cls.var += 1.
As pointed out by the other answers, the var is also inherited to b and c.
By using cls.var += 1 both subclasses increase their own var instead of a's var.
class a:
var = 0
#classmethod
def incr(cls):
print(cls.var)
a.var += 1
class b(a):
def f(self):
super().incr()
class c(a):
def f(self):
super().incr()
cb = b()
cc = c()
cb.incr()
cb.incr()
cb.incr()
cc.incr()
cc.incr()
cc.incr()
cb.incr()
cc.incr()
Produces:
0
1
2
3
4
5
6
7
Both class b and class c inherit from class a separately, and var is set to 0 each time.
One way to have class c to get the same value of var in class a as class b does, class c can inherit from class b like so:
class a(object):
var = 0
#classmethod
def incr(cls):
print cls.var
cls.var+=1
class b(a):
def func(self):
super(b,self).incr()
class c(b):
def func(self):
super(c,self).incr()
t = a()
t1 = b()
t2 = c()
t1.func()
t1.func()
t1.func()
t2.func()
t2.func()
t2.func()`
I have the following Python code. How do I get c to return 2 without changing the class definitions? Or differently stated. How to access overridden methods in an object with multiple inheritance?
class A(object):
def foo(self):
return 1
class B(object):
def foo(self):
return 2
class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
c = C()
c.foo() # Returns 1
Just after asking this question I got an answer elsewhere so here goes:
import types
c.foo = types.MethodType(B.foo, c)
You can call the unbound method explicitly:
>> B.foo(c)
2