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.
I need to define a variable of a class a class object. How can I do it?
if for example I have a class like this :
class A:
def __init__(self, a, b):
self.a = a
self.b = b
and I want to create another class B that have a variable as instance of class A like :
class B:
def __init__(self, c = A(), d):
self.c = c
self.d = d
How can I do it ? I need to do particular operation or simply declarate c as object of class A when I create the object of class B ?
class B:
def __init__(self, a, b, d):
self.c = A(a, b)
self.d = d
or
class B:
def __init__(self, c, d):
self.c = c
self.d = d
or
class B:
def __init__(self, d):
self.c = A(a, b) # a and b can be values
self.d = d
What you wrote mostly works:
def __init__(self, c = A(), d):
self.c = c
But there's a "gotcha" which you really want to avoid.
The A constructor will be evaluated just once,
at def time, rather than each time you construct
a new B object.
That's typically not what a novice coder wants.
That signature mentions a mutable default arg,
something it's usually best to avoid,
if only to save future maintainers from
doing some frustrating debugging.
https://dollardhingra.com/blog/python-mutable-default-arguments/
https://towardsdatascience.com/python-pitfall-mutable-default-arguments-9385e8265422
Instead, phrase it this way:
class B:
def __init__(self, c = None, d):
self.c = A(1, 2) if c is None else c
...
That way the A constructor will be evaluated afresh each time.
(Also, it would be good to supply both of A's mandatory arguments.)
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'>]
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.
This question is specifically in the context of Python classes here, but could be more generalized. I am creating a class m that will be initialized with four variables, which I would then like to assign to the instantiation of the class. Right now, this looks like this:
class mm(object):
def __init__(self, a, b, c, r):
self.a = a
self.b = b
self.c = c
self.r = r
# etc.
However, I would like to be able to assign the variables to self in one line, something like the pseudocode:
def __init__(self, a, b, c, r):
self.varname = var for var in [a, b, c, r]
It would be even better if this could be generalized to an arbitrary number of variables, so that you could get something like:
def __init__(self, **kwargs):
assign(self, varname, value) for varname, value in kwargs.iteritems()
However, as far as I know, it's not really possible to loop through statements like that. Does anyone know a way to do this?
You can use setattr:
class mm(object):
def __init__(self, **kwargs):
for varname, value in kwargs.iteritems():
setattr(self, varname, value)
You can also use the instance's namespace dictionary for a mass assignment:
class mm(object):
def __init__(self, **kwargs):
self.__dict__.update(kwargs)