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

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'>]

Related

How does multiple inheritance in python work with each parent class taking arguments?

Say I want to create a class with two parent classes as follows:
class A:
def __init__(self, a) -> None:
self.a = a
class B:
def __init__(self, b) -> None:
self.b = b
class C(A, B):
def __init__(self, a, b, c) -> None:
super().__init__(a)
super().__init__(b)
self.c = c
Now, how do I make sure a goes to A and b goes to B because if I try to run this:
c = C(1, 2, 3)
print(c.a, c.b, c.c, sep='\n')
It yells AttributeError: 'C' object has no attribute 'b'.
Do I always have to create a sub-class with multiple parents like below?
class C(A, B):
def __init__(self, a, b, c) -> None:
A.__init__(self, a)
B.__init__(self, b)
self.c = c
If so, then what is the use of super?
I've also noticed that if there are no arguments, calling C() will also call __init__ from the first parent i.e. A without doing any super or A.__init__(). But how do I call B here? Again I'm unable to understand the use of super.
It might be an ambiguous question but I'm really finding hard to get answers here. Thanks in advance for any help.

Maintaining readability when using super() for direct multiple inheritance

For the case of the most basic multiple inheritance:
class A:
def __init__(self, a):
self.a = a
class B:
def __init__(self, b):
self.b = b
class C(A, B):
def __init__(self, a, b):
A.__init__(self, a)
B.__init__(self, b)
I do not see why super() should be used. I suppose you could implement it with kwargs, but that is surely less readable than the above method. I am yet to find any answers on stack overflow which are in favour of this method, yet surely for this case it is the most satisfactory?
There are a lot of questions marked as duplicate on this topic, but no satisfactory answers for this exact case. This question addresses multiple inheritance and the use of super() for a diamond inheritance. In this case there is no diamond inheritance and neither parent class have any knowledge of each other, so they shouldn't need to call super() like this suggests.
This answer deals with the use of super in this scenario but without passing arguments to __init__ like is done here, and this answer deals with passing arguments but is again a diamond inheritance.
One correct way to use super here would be
class A:
def __init__(self, a, **kwargs):
super().__init__(**kwargs)
self.a = a
class B:
def __init__(self, b, **kwargs):
super().__init__(**kwargs)
self.b = b
class C1(A, B):
pass
class C2(A, B):
def __init__(self, a, b, **kwargs):
super().__init__(a=a, b=b, **kwargs)
c1 = C1(a="foo", b="bar")
c2 = C2(a="foo", b="bar")
The method resolution order for C is [C, A, B, object]. Each time super() is called, it returns a proxy for the next class in the MRO, based on where super() is called at the time.
You have two options when defining C, depending on whether you want to define C.__init__ with a signature that mentions the two arguments A and B required for initialization. With C1, C1.__init__ is not defined so A.__init__ will be called instead. With C2, you need to explicitly call the next __init__ method in the chain.
C, knowing that it is a subclass of A and B, has to at least provide the expected arguments for the known upstream __init__ methods.
A.__init__ will pass on everything except a to the next class's __init__ method.
B.__init__ will pass on everything it receives except b.
object.__init__ will finally be called, and assuming all previous classes correctly removed the keyword arguments they introduced, will receive no additional arguments.
Changing the order in which the various __init__s are called means changing the MRO, which means altering the order of the base classes. If you want more control than that, then cooperative multiple inheritance is not for you.
class A(object):
def __init__(self, *args, **kwargs):
super(A, self).__init__(*args, **kwargs)
self.a = kwargs['a']
class B(object):
def __init__(self, *args, **kwargs):
super(B, self).__init__()
self.b = kwargs['b']
class C(A, B):
def __init__(self, *args, **kwargs):
super(C, self).__init__(*args, **kwargs)
z = C(a=1,b=2)
z.b
2

Python encapsulate data for a class

I have two python classes, A and B that inherits from A.
At runtime, I only have one instance of class A, but many instances of class B.
class A:
def __init__(self, a):
self.a = a
def _init2 (self, AA)
self.a = AA.a
class B(A):
def __init__(self, AA, b):
super()._init2(AA)
self.b = b
AA = A(0)
BB = B(AA, 1)
Is this the good way of writing it ? It seems ugly ...
It would probably be better to remove init2 and only use __init__. Having both is confusing and unnatural.
class A:
def __init__(self, obj):
# I believe that this is what you tried to achieve
if isinstance(obj, type(self)):
self.a = obj.a
else:
self.a = obj
class B(A):
def __init__(self, A, b):
super().__init__(A)
self.b = b
On a side note, there are too many things called A here. The A in def __init__(self, A, b): is most probably not referring to the A that you expect.

Inheritance inside Classes Python3

I need something like this
class Parent(object):
class Base(object):
def __init__(self, a, b):
self.a = a
self.b = b
class Derived(Base):
def __init__(self, a, b, c):
super(Derived,self).__init__(a, b)
self.c = c
def doit():
pass
parent = Parent()
derived = parent.Derived(x,y,z)
derived.doit()
When I try to run this, i get this following error: NameError: name 'Derived' is not defined
I tried with 'Base' in the place of 'Derived' in super() - didn't help
Class inheritance does not change the parent class. In this case your Parent class only contains the original Base class and not the derived class.
You can simply use monkey-patching to solve this problem,
class Parent(object):
pass
class Base(object):
def __init__(self, a, b):
self.a = a
self.b = b
class Derived(Base):
def __init__(self, a, b, c):
super(Derived,self).__init__(a, b)
self.c = c
def doit(self):
pass
Parent.Derived = Derived
parent = Parent()
x, y , z = 1, 1, 1
derived = parent.Derived(x,y,z)
derived.doit()
Prefixing 'Derived' with 'Parent.', made it. As I already have commented on the question. This is just for experimenting with the 'Derived' class. But I am still wondering how the, 'class Derived(Base):' is fine (without 'Parent.' prefix for 'Base' class)
class Parent(object):
class Base(object):
def __init__(self, a, b):
self.a = a
self.b = b
class Derived(Base):
def __init__(self, a, b, c):
super(Parent.Derived,self).__init__(a, b)
self.c = c
def doit():
pass
parent = Parent()
derived = parent.Derived(x,y,z)
derived.doit()

Python - How to use underlying object's constructor as class method?

Please forgive the bad title - I had a hard time trying to think of a concise way to explain this.
I have a Python class that will have some underlying objects of other classes. I want to be able to create these underlying objects via a method of the original object. Let me try to explain better with an example:
class Foo:
def __init__(self):
self.bars = []
def Bar(self, a, b, c):
self.bars.append(Bar(a, b, c))
class Bar:
def __init__(self, a, b, c):
self.a = a
self.b = b
self.c = c
I would use the above as such:
f = Foo()
f.Bar(1, 2, 3)
So this works how I want but is kind of crappy with respect to maintenance. Is there a nice "Pythonic" way to do this that would make maintaining this easy? For instance, let's say I changed the constructor of Bar to:
__init__(self, a, b, c, d):
would there be a way to define all of this so I don't have to update the argument list in 3 places?
Sure, no problem: Just pass *args and **kwargs on to Bar:
class Foo:
def __init__(self):
self.bars = []
def append_bar(self, *args, **kwargs):
self.bars.append(Bar(*args, **kwargs))
class Bar:
def __init__(self, a, b, c, d):
self.a = a
self.b = b
self.c = c
self.d = d
f=Foo()
f.append_bar(1,2,3,4)
PS. I changed the name of the method to append_bar because the usual convention in Python is to use lowercase names for methods, and I think methods whose names are verbs help describe what the method does.

Categories

Resources