Python Multiple Inheritance super().__init__() - python

I have two classes, with the same parameter initialized by their __init__ method.
I would like to inherit both classes in class "X". But I will get: TypeError: B.__init__() missing 1 required positional argument: 'my_param'
Reproducible Example:
class A:
def __init__(self, my_param):
super().__init__()
self.my_param = my_param
class B:
def __init__(self, my_param):
super().__init__()
self.my_param = my_param * 2
class X(A, B):
def __init__(self, my_param):
super().__init__(my_param=my_param)
a = X(my_param=1)
print(a.my_param)
A and B are Mixins, they provide additional functionality for Child Classes. They can be used separetly or together. Lets's say class A provides searhc functionality for search by ID, where class B provides search by value.
Is there a way to set my_param for each of A and B or to set it without getting the error?

This can't be done just using super() calls, because the superclass of the last class in the chain will be object, which doesn't take a my_param parameter.
See python multiple inheritance passing arguments to constructors using super for more discussion of parameter passing to __init__() methods with multiple inheritance.
So you need to change X to call the superclass init methods explicitly, rather than using super(). And A and B shouldn't call super().__init__() because they're subclasses of object, which doesn't do anything in its __init__().
class A:
def __init__(self, my_param):
self.my_param = my_param
class B:
def __init__(self, my_param):
self.my_param = my_param * 2
class X(A, B):
def __init__(self, my_param):
A.__init__(self, my_param=my_param)
B.__init__(self, my_param=my_param)

Since A and B share the same fields, I think it makes sense to make one inherit from the other - in this case, B inherit from A. That way, you'll only need to subclass from B in class X.
For example:
class A:
def __init__(self, my_param):
self.my_param = my_param
class B(A):
def __init__(self, my_param):
super().__init__(my_param * 2)
# or:
# A.__init__(self, my_param * 2)
class X(B):
def __init__(self, my_param):
super().__init__(my_param=my_param)
a = X(my_param=1)
print(a.my_param) # 2

Related

Correct way of returning new class object (which could also be extended)

I am trying to find a good way for returning a (new) class object in class method that can be extended as well.
I have a class (classA) which has among other methods, a method that returns a new classA object after some processing
class classA:
def __init__(): ...
def methodX(self, **kwargs):
process data
return classA(new params)
Now, I am extending this class to another classB. I need methodX to do the same, but return classB this time, instead of classA
class classB(classA):
def __init__(self, params):
super().__init__(params)
self.newParams = XYZ
def methodX(self, **kwargs):
???
This may be something trivial but I simply cannot figure it out. In the end I dont want to rewrite the methodX each time the class gets extended.
Thank you for your time.
Use the __class__ attribute like this:
class A:
def __init__(self, **kwargs):
self.kwargs = kwargs
def methodX(self, **kwargs):
#do stuff with kwargs
return self.__class__(**kwargs)
def __repr__(self):
return f'{self.__class__}({self.kwargs})'
class B(A):
pass
a = A(foo='bar')
ax = a.methodX(gee='whiz')
b = B(yee='haw')
bx = b.methodX(cool='beans')
print(a)
print(ax)
print(b)
print(bx)
class classA:
def __init__(self, x):
self.x = x
def createNew(self, y):
t = type(self)
return t(y)
class classB(classA):
def __init__(self, params):
super().__init__(params)
a = classA(1)
newA = a.createNew(2)
b = classB(1)
newB = b.createNew(2)
print(type(newB))
# <class '__main__.classB'>
I want to propose what I think is the cleanest approach, albeit similar to existing answers. The problem feels like a good fit for a class method:
class A:
#classmethod
def method_x(cls, **kwargs):
return cls(<init params>)
Using the #classmethod decorator ensures that the first input (traditionally named cls) will refer to the Class to which the method belongs, rather than the instance.
(usually we call the first method input self and this refers to the instance to which the method belongs)
Because cls refers to A, rather than an instance of A, we can call cls() as we would call A().
However, in a class that inherits from A, cls will instead refer to the child class, as required:
class A:
def __init__(self, x):
self.x = x
#classmethod
def make_new(cls, **kwargs):
y = kwargs["y"]
return cls(y) # returns A(y) here
class B(A):
def __init__(self, x):
super().__init__(x)
self.z = 3 * x
inst = B(1).make_new(y=7)
print(inst.x, inst.z)
And now you can expect that print statement to produce 7 21.
That inst.z exists should confirm for you that the make_new call (which was only defined on A and inherited unaltered by B) has indeed made an instance of B.
However, there's something I must point out. Inheriting the unaltered make_new method only works because the __init__ method on B has the same call signature as the method on A. If this weren't the case then the call to cls might have had to be altered.
This can be circumvented by allowing **kwargs on the __init__ method and passing generic **kwargs into cls() in the parent class:
class A:
def __init__(self, **kwargs):
self.x = kwargs["x"]
#classmethod
def make_new(cls, **kwargs):
return cls(**kwargs)
class B(A):
def __init__(self, x, w):
super().__init__(x=x)
self.w = w
inst = B(1,2).make_new(x="spam", w="spam")
print(inst.x, inst.w)
Here we were able to give B a different (more restrictive!) signature.
This illustrates a general principle, which is that parent classes will typically be more abstract/less specific than their children.
It follows that, if you want two classes that substantially share behaviour but which do quite specific different things, it will be better to create three classes: one rather abstract one that defines the behaviour-in-common, and two children that give you the specific behaviours you want.

Call the super constructors of parent classes in python [duplicate]

Consider the following snippet of python code
class A(object):
def __init__(self, a):
self.a = a
class B(A):
def __init__(self, a, b):
super(B, self).__init__(a)
self.b = b
class C(A):
def __init__(self, a, c):
super(C, self).__init__(a)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
#super(D,self).__init__(a, b, c) ???
self.d = d
I am wondering how can I pass a, b and c to corresponding base classes' constructors.
Well, when dealing with multiple inheritance in general, your base classes (unfortunately) should be designed for multiple inheritance. Classes B and C in your example aren't, and thus you couldn't find a proper way to apply super in D.
One of the common ways of designing your base classes for multiple inheritance, is for the middle-level base classes to accept extra args in their __init__ method, which they are not intending to use, and pass them along to their super call.
Here's one way to do it in python:
class A(object):
def __init__(self,a):
self.a=a
class B(A):
def __init__(self,b,**kw):
self.b=b
super(B,self).__init__(**kw)
class C(A):
def __init__(self,c,**kw):
self.c=c
super(C,self).__init__(**kw)
class D(B,C):
def __init__(self,a,b,c,d):
super(D,self).__init__(a=a,b=b,c=c)
self.d=d
This can be viewed as disappointing, but that's just the way it is.
Unfortunately, there is no way to make this work using super() without changing the Base classes. Any call to the constructors for B or C is going to try and call the next class in the Method Resolution Order, which will always be B or C instead of the A class that the B and C class constructors assume.
The alternative is to call the constructors explicitly without the use of super() in each class.
class A(object):
def __init__(self, a):
object.__init__()
self.a = a
class B(A):
def __init__(self, a, b):
A.__init__(self, a)
self.b = b
class C(A):
def __init__(self, a, c):
A.__init__(self, a)
self.c = c
class D(B, C):
def __init__(self, a, b, c, d):
B.__init__(self, a, b)
C.__init__(self, a, c)
self.d = d
There is still a downside here as the A constructor would be called twice, which doesn't really have much of an effect in this example, but can cause issues in more complex constructors. You can include a check to prevent the constructor from running more than once.
class A(object):
def __init__(self, a):
if hasattr(self, 'a'):
return
# Normal constructor.
Some would call this a shortcoming of super(), and it is in some sense, but it's also just a shortcoming of multiple inheritance in general. Diamond inheritance patterns are often prone to errors. And a lot of the workarounds for them lead to even more confusing and error-prone code. Sometimes, the best answer is to try and refactor your code to use less multiple inheritance.
A key concept: super does not refer to the parent class. It refers to the next class in the mro list, which depends on the actual class being instantiated.
So when calling super().__init__, the actual method called is undetermined from the calling frame.
That's why the classes have to be specially designed for mixin.
Even a class witch inherits only from object, should call super().__init__.
And of course, when object__init__(**kwargs) is called, kwargs should be empty by then; else case an error will raise.
Example:
class AMix:
def __init__(self, a, **kwargs):
super().__init__(**kwargs)
self.a = a
class BMix:
def __init__(self, b, **kwargs):
super().__init__(**kwargs)
self.b = b
class AB(AMix, BMix):
def __init__(self, a, b):
super().__init__(a=a, b=b)
ab = AB('a1', 'b2')
print(ab.a, ab.b) # -> a1 b2
I was not completely satisfied with the answers here, because sometimes it gets quite handy to call super() for each of the base classes separately with different parameters without restructuring them. Hence, I created a package called multinherit and you can easily solve this issue with the package. https://github.com/DovaX/multinherit
from multinherit.multinherit import multi_super
class A(object):
def __init__(self, a):
self.a = a
print(self.a)
class B(A):
def __init__(self, a, b):
multi_super(A,self,a=a)
self.b = b
print(self.b)
class C(A):
def __init__(self, a, c):
multi_super(A,self,a=a)
self.c = c
print(self.c)
class D(B, C):
def __init__(self, a, b, c, d):
multi_super(B,self,a=a,b=b)
multi_super(C,self,a=a,c=c)
self.d = d
print(self.d)
print()
print("d3")
d3=D(1,2,3,4)
print(d3._classes_initialized)
>>> d3
>>> 1
>>> 2
>>> 3
>>> 4
>>> [<class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>]

Method resolution order in Python - in absence of constructor in child and parent left class

This is about Method resolution order in Python. Lets say we have 3 classes: A, B, C.
C(A,B) - multiple inheritance.
In the absence of constructor in the child class C, and parent left class A, does the python engine look for the constructor from the RHS class B and execute it if it is present OR does it look to see if A has any parent class with constructor?
Example: What if Class A inherits from Class X that has a constructor. Then will the constructor from Class X run or the constructor from Class B run?
class A:
pass
class B:
def __init__(self):
print("I am class B")
class C(A, B):
pass
print(C())
I am class B
With this other example, you will understand better how multiple inheritance works:
class A:
def __init__(self):
print("I am class A")
class B:
def __init__(self):
print("I am class B")
class C(A, B):
pass
print(C())
I am class A
As you can see, B __init__ didn't get called, this is because parent classes are called from left to right, but each one must not forget to call "super init", otherwise it breaks the inheritance chain
class A:
def __init__(self):
print("I am class A")
super().__init__()
class B:
def __init__(self):
print("I am class B")
class C(A, B):
pass
print(C())
I am class A
I am class B

python signature of method in diamond derived class

I am wondering what is the signature in a class derived from 2 classes that have a common but different method.
For example
class Base1():
def __init__(a, b):
self.a = a
self.b = b
class Base2():
def __init__(c):
self.c = c
class Derived(Base1, Base2):
def do_something():
return 3
What is the signature of the init method is the Derived class?
What is its implementation?
There won't be a new signature. Instead, the first method in the method resolution order will be used. In your case that's Base1.__init__.

Python Multiple Inheritance/Mixin

I have the following problem:
class A:
animal = 'gerbil'
def __init__(self):
self.result = self.calculate_animal()
def calculate_animal(self):
print(self.animal)
return self.animal
class B(A):
animal = 'zebra'
def __init__(self):
super(B, self).__init__()
Now, I want a certain set of subclasses from A, to implement a new function that calculates something different with the animal, like so:
class CapitalizeAnimal:
def calculate_animal(self):
self.animal = self.animal.upper()
# I need to call some version of super().self.animal,
# but how will this Mixin class know of class A?
class C(A, #CapitalizeAnimal?):
animal = 'puma':
def __init__(self):
super(C, self).__init__()
How do I get class C to implement the CapitalizeAnimal version of calculate_animal, while keeping its animal as puma? I'm confused at how the Mixin class will be able to call a super() function.
The order of the parent classes is important, you should do it like so:
class C(CapitalizeAnimal, A):
animal = 'puma'
def __init__(self):
super(C, self).__init__()
More info can be found by reading about the MRO (Method Resolution Order).
Also, super only works with new style classes, so you should make A inherit object (unless of course you are using Python 3).
First of all, B and C don't need __init__() if the only action is calling the super __init__.
To your question: Have you tried class C(A, CapitalizeAnimal): and/or class C(A, CapitalizeAnimal):? I.e., omitting the # and the ??

Categories

Resources