Super with arguments in case of multiple inheritance - python

I have a class that inherits from two other classes whose __init__ take both a parameter like this:
class A(object):
def __init__(self, a):
self.a = a
class B(object):
def __init__(self, b):
self.b = b
class C(A, B):
def __init__(self, a, b):
super(C, self).__init__(a, b)
c = ClassC(1, 2)
This gives a TypeError: __init__() takes exactly 2 arguments (3 given).
When setting b in B to a fixed value and passing only 1 parameter to super then trying to access b in C gives an AttributeError: 'ClassC' object has no attribute 'b':
class A(object):
def __init__(self, a):
self.a = a
class B(object):
def __init__(self, b):
self.b = 2
class C(A, B):
def __init__(self, a, b):
super(C, self).__init__(a)
print self.a
print self.b
c = ClassC(1, 2)
When calling the __init__ manually everything seems to be fine:
class A(object):
def __init__(self, a):
self.a = a
class B(object):
def __init__(self, b):
self.b = b
class C(A, B):
def __init__(self, a, b):
A.__init__(a)
B.__init__(b)
print self.a
print self.b
c = ClassC(1, 2)
So how can I get this inheritance straight and how can I manage the parameters for __init__ of inherited classes when using super? Is it even possible? How does super know which parameters are to pass to which class?

Related

python class B inherit class A with all its methods but change one method only

Let's say class A has 10 methods. Some of the methods are private and it has private attributes as well. I want to create class B so I can change last method only without duplicating the code for the rest of the methods. My example is below. At the moment I am unable to achieve it with such inheritance as I get AttributeError: 'B' object has no attribute '_B__c'
class A:
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self.__c = self.a + self.b
def get_data(self):
return self.__c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__c = self.__modify_data()
def __modify_data(self):
self.__c += 10000
def get_data(self):
return self.__c
b = B(a=5, b=10).get_data()
Question 2:
Can I achieve it with use of *args so I do not have to repeat all the arguments?
EDIT:
Please see my updated code above.
I believe private attributes causes the problem.
Can I solve it with still using private?
class A(object):
def __init__(self, a=1, b=2):
self.a = a
self.b = b
self.__foo()
def __foo(self):
self._c = self.a + self.b
def get_data(self):
return self._c
class B(A):
def __init__(self, *args, **kwargs):
super(B, self).__init__(*args, **kwargs)
self.__modify_data()
def __modify_data(self):
self._c += 10000
b = B(a=5, b=10).get_data()
print(b)
Output:
10015
Changing _c to __c gives AttributeError: 'B' object has no attribute '_B__c'
Yes, the __ is causing the trouble by making variable c inaccessible in children, which is good because the private variable of parents should not be allowed to edit by the children class.

Understanding super in Python

Could you please explain to me how to write the deadly diamond in Python? I saw many examples of similar code without using constructor arguments, but once I start using arguments thigs start being messy...
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
self.b = b
super().__init__(a)
class C(A):
def __init__(self, a, c):
self.c = c
super().__init__(a)
class D(B, C):
def __init__(self, a, b, c, d):
self.d = d
# How do I pass a and b to B.__init__
# and a and c to C.__init__
# using super() ?
super().__init__(a, b, c) #???
d = D(1, 2, 3, 4)
The simplest way would probably be that every subclass accepts a dict of kwargs, and passes it to the upper level:
class A:
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, b, **kwargs):
self.b = b
super().__init__(**kwargs)
class C(A):
def __init__(self, c, **kwargs):
self.c = c
super().__init__(**kwargs)
class D(B, C):
def __init__(self, d, **kwargs):
self.d = d
super().__init__(**kwargs)
d = D(a=1, b=2, c=3, d=4)
print(d.a, d.b, d.c, d.d)
# 1 2 3 4
At each call, __init__ gets the argument it requires, and passes the remaining ones to the parent class. The only drawback is that you have to pass the arguments as keywords.
Your subclasses initializers don't have compatible signatures, which is the key to proper cooperative super calls.
The simple solution here is to make those signatures compatible - canonically by adding support for varargs and arbitrary keyword args all along the chain, ie:
# NB: Python3 required, won't work in Py2
class A:
def __init__(self, a, *args, **kwargs):
self.a = a
class B(A):
def __init__(self, a, b, *args, **kwargs):
super().__init__(a, *args, **kwargs)
self.b = b
class C(A):
def __init__(self, a, c, *args, **kwargs):
super().__init__(a, *args, **kwargs)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d, *args, **kwargs):
super().__init__(a, b=b, c=c, *args, **kwargs)
self.d = d
d = D(1, 2, 3, 4)
This will of course not solve each and every possible issue with multiple inheritance and cooperative super calls, but MI is tricky, and there are restrictions to what's possible anyway.

Passing arguments to superclass constructor without repeating them in childclass constructor

class P(object):
def __init__(self, a, b):
self.a = a
self.b = b
class C(P):
def __init__(self, c):
P.__init__()
self.c = c
obj = C(a, b, c) #want to instantiate a C with something like this
I want to define C class object without rewriting all the P class constructor argument in C's constructor, but the above code doesn't seem to work. What is the right approach to do this?
Clarification:
The idea is to avoid putting parent class's constructor arguments in child class's constructor. It's just repeating too much. All my parent and child classes have many arguments to take in for constructors, so repeating them again and again is not very productive and difficult to maintain. I'm trying to see if I can only define what's unique for the child class in its constructor, but still initialize inherited attributes.
In Python2, you write
class C(P):
def __init__(self, a, b, c):
super(C, self).__init__(a, b)
self.c = c
where the first argument to super is the child class and the second argument is the instance of the object which you want to have a reference to as an instance of its parent class.
In Python 3, super has superpowers and you can write
class C(P):
def __init__(self, a, b, c):
super().__init__(a, b)
self.c = c
Demo:
obj = C(1, 2, 3)
print(obj.a, obj.b, obj.c) # 1 2 3
Response to your comment:
You could achieve that effect with the *args or **kwargs syntax, for example:
class C(P):
def __init__(self, c, *args):
super(C, self).__init__(*args)
self.c = c
obj = C(3, 1, 2)
print(obj.a, obj.b, obj.c) # 1 2 3
or
class C(P):
def __init__(self, c, **kwargs):
super(C, self).__init__(**kwargs)
self.c = c
obj = C(3, a=1, b=2)
print(obj.a, obj.b, obj.c) # 1 2 3
obj = C(a=1, b=2, c=3)
print(obj.a, obj.b, obj.c) # 1 2 3
You can call parent class constructor by passing self and required arguments
class C(P):
def __init__(self, a,b,c):
P.__init__(self,a,b)
self.c = c

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

How can I call a particular base class method in Python?

Let's say, I have the following two classes:
class A(object):
def __init__(self, i):
self.i = i
class B(object):
def __init__(self, j):
self.j = j
class C(A, B):
def __init__(self):
super(C, self).__init__(self, 4)
c = C()
c will only have the i attribute set, not the j.
What should I write to set both of attributes/only the j attribute?
If you want to set only the j attribute, then only call B.__init__:
class C(A, B):
def __init__(self):
B.__init__(self,4)
If you want to manually call both A and B's __init__ methods, then
of course you could do this:
class C(A, B):
def __init__(self):
A.__init__(self,4)
B.__init__(self,4)
Using super is a bit tricky (in particular, see the section entitled "Argument passing, argh!"). If you still want to use super, here is one way you could do it:
class D(object):
def __init__(self, i):
pass
class A(D):
def __init__(self, i):
super(A,self).__init__(i)
self.i = i
class B(D):
def __init__(self, j):
super(B,self).__init__(j)
self.j = j
class C(A, B):
def __init__(self):
super(C, self).__init__(4)
c = C()
print(c.i,c.j)
# (4, 4)

Categories