Python solves the diamond problem well if there are no fields in the classes by linearizing the method resolution order. However, if the classes have fields then how do you call the super constructors? Consider:
class A:
def __init__(self, a):
self.a = a # Should only be initialized once.
class B(A):
def __init__(self, a, b):
super().__init__(a)
self.b = b
class C(A):
def __init__(self, a, c, b=None):
super().__init__(a)
self.c = c
class D(C, B):
def __init__(self, a, b, c):
super().??? # What do you put in here.
For my use case I do actually have a solution, because b can't be None in the application and therefore the following largely works:
class A:
def __init__(self, a):
self.a = a # Should only be initialized once.
class B(A):
def __init__(self, a, b):
assert b is not None # Special case of `b` can't be `None`.
super().__init__(a)
self.b = b
class C(A):
def __init__(self, a, c, b=None): # Special init with default sentinel `b`.
if b is None:
super().__init__(a) # Normally `C`'s super is `A`.
else:
super().__init__(a, b) # From `D` though, `C`'s super is `B`.
self.c = c
class D(C, B): # Note order, `C`'s init is super init.
def __init__(self, a, b, c):
super().__init__(a, c, b)
def main():
A('a')
B('b', 1)
C('c', 2)
D('d', 3, 4)
C('c2', 5, 6) # TypeError: __init__() takes 2 positional arguments but 3 were given
This largely works for the special case of b can't be None, however it still has a problem if C's __init__ is called directly (see last line of above). Also you have to modify C for the multiple inheritance and you have to inherit in the order C, B.
==== Edit ===
Another possibility is to manually initialize each field (this is somewhat similar to how Scala handles fields under the covers).
class A0:
def __init__(self, a): # Special separate init of `a`.
self._init_a(a)
def _init_a(self, a):
self.a = a
class B0(A0):
def __init__(self, a, b): # Special separate init of `b`.
self._init_a(a)
self._init_b(b)
def _init_b(self, b):
self.b = b
class C0(A0):
def __init__(self, a, c): # Special separate init of `c`.
self._init_a(a)
self._init_c(c)
def _init_c(self, c):
self.c = c
class D0(C0, B0):
def __init__(self, a, b, c): # Uses special separate inits of `a`, `b`, and `c`.
self._init_a(a)
self._init_b(b)
self._init_c(c)
The disadvantage of this approach is that it is very non-standard, to the extent that PyCharm gives a warning about not calling super init.
==== End edit ===
Is there a better way?
Thanks in advance for any help, Howard.
Related
So let's say I have this code structure:
class Parent:
def __init__(self, a, b, c):
self.a = a,
self.b = b
self.c = c
#classmethod
def from_string(cls, some_string):
if some_string == 'duh':
return cls(a=5, b=6, c='banana')
else:
return cls(a=0, b=0, c='chicken')
def method_1(self):
#do something
def method_2(self):
#do something else
class FirstChild(Parent):
def __init__(self, a, b, c):
super().__init__(a, b, c)
def child_specific_method(self):
#do something
class SecondChild(Parent):
def __init__(self, a, b):
super().__init__(a, b)
def some_other_method(self):
#do stuff
My thinking was that I want both subclasses to have access to methods of the Parent class, but also extend its functionality. At the same time I want the Parent class to instantiate with different parameters based on the class method.
Now I'm confused as to how I would create instances of child classes? Or, more precisely, how would I create child instances when there can be different versions of the parent class?
I have the following situation:
class Foo:
def __init__(self, O):
self.a = O.some_attr.calc_a()
self.b = O.some_other_attr.calc_b()
Note that O cannot be reconstructed from a and b. Now, I also want to be able to initialise Foo directly by passing a and b, but I only want to do this internally, the standard way should be by passing O.
I know I can do something like
class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
#classmethod
def from_O(cls, O):
return cls(O.some_attr.cal_a(), O.some_other_attr.cal_b())
but this has the disadvantage that the standard call now becomes the more cumbersome Foo.from_O(O).
In other words, how can I achieve Foo(O) and Foo.from_a_b(a,b) when O is not reconstructable from a and b?
Can I have a classmethod that avoids calling __init__?
(Note: I am looking for a 'clean' way to do this. I know I can dissect the argument list or do something like
class _Foo:
def __init__(self, a, b):
self.a = a
self.b = b
class Foo(_Foo):
def __init__(self, O):
super().__init__(O.some_attr.cal_a(), O.some_other_attr.cal_b())
but this seems a rather awkward solution.)
You could make O, a, and b all optional arguments to the same __init__ method and make a distinction on whether O is given or not.
class Foo:
def __init__(self, O=None, a=None, b=None):
if O is not None:
self.a = O.some_attr.calc_a()
self.b = O.some_other_attr.calc_b()
# ignore a and b
else:
if a is None or b is None:
raise TypeError("If O is not given, a and b cannot be None")
self.a = a
self.b = b
Usage:
# from O
foo_from_O = Foo(O)
# from a, b
foo_from_a_b_1 = Foo(None, 'a', 'b')
foo_from_a_b_2 = Foo(a='a', b='b')
Let's say I have parent class P
class P(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
If a create another class C which is a child class of P, is it possible to inherit SOME parameters of P but not all (Let's say I just want parameters a,c from class P to be passed on to C). I know it's a weird question, and I don't know if there's an application to this, but I just can't seem to find an answer. Thank you in advance!
The only good way to do it as far as I know is to do something like the following:
class P:
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
class C(P):
def __init__(self, a, b, c):
super(C, self).__init__(a, b, c)
self._b = b
Essentially, you call the superclass constructor to define all of the values, but then override the value (in this case, for self._b) later in the child class constructor.
Alternatively, if you don't want self._b to even be a thing in the class C, you can also do del self._b after the super().__init__() call to remove it altogether.
In general, though, it seems like bad practice to have a child class not have some of the fields that the parent class has, since other parts of the code may rely on that field being there.
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.
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