This seems like such a simple task, but I've spend way too much time on this now, without a solution. Here's the setup:
class A(object):
def __init__(self, x=0):
print("A.__init__(x=%d)" % x)
class B(object):
def __init__(self, y=1):
print("B.__init__(y=%d)" % y)
class C(A, B):
def __init__(self, x=2, y=3, z=4):
super().__init__(x=x, y=y)
print("C.__init__(z=%d)" % z)
That's the idea, but of course this results in
TypeError: __init__() got an unexpected keyword argument 'y'
All other attempts have failed in a similar manner, and I could find no resource on the internet with the correct solution. The only solutions included replacing all init params with *args, **kwargs
. This does not really suit my needs.
As per request, a real world example:
(This uses a different approach, which has a valid syntax, but produces unwanted results.)
from PyQt5.QtCore import QObject
class Settings(object):
def __init__(self, file):
self.file = file
class SettingsObject(object):
def __init__(self, settings=None):
print("Super Init", settings is None)
self.settings = settings
class MyObject(QObject, SettingsObject):
def __init__(self, param):
print("Entering Init")
QObject.__init__(self)
SettingsObject.__init__(self, settings=Settings(__file__))
self.param = param
print("Leaving Init")
Result:
Entering Init
Super Init True
Super Init False
Leaving Init
I would like the line Super Init True
to disappear.
It seems there's a few things you're confused about, so:
Why do you get TypeError: __init__() got an unexpected keyword argument 'y'
?
Because the next implementation of __init__
in the method resolution order (MRO) is A.__init__
, which only accepts x
. The MRO is C
-> A
-> B
, so you must make A.__init__
accept y
(either specifically or using **kwargs
) and pass it on (using super
again) to B.__init__
.
super
doesn't call every implementation, and certainly doesn't ensure they all get only the arguments they're expecting, it just calls the next implementation with all of the parameters it's given.
Why does SettingsObject.__init__
get called twice, once with and once without settings
?
It would appear that QObject.__init__
includes a call to super().__init__
, which invokes SettingsObject.__init__
as that's next in MyObject
's MRO. However it doesn't pass along any settings
parameter, so the first time it's invoked you see settings is None
. The second time you're invoking it directly, and passing settings
explicitly, so you see settings is not None
.
How do you write mix-in classes?
I think this is the question you should really be asking. SettingsObject
should be a mix-in class, and therefore be designed correctly to collaborate with other classes in the hierarchy it's mixed into. In this case:
class SettingsObject: # (object) isn't needed in 3.x
def __init__(self, *args, settings=None, **kwargs):
super().__init__(*args, **kwargs)
self.settings = settings
It looks from your example like QObject.__init__
doesn't have any required parameters, but you should still write the mix-in to play nice in case of optional parameters or reuse elsewhere. Then the MyObject
implementation looks like:
class MyObject(SettingsObject, QObject):
def __init__(self, param):
super().__init__(settings=Settings(file))
self.param = param
Note that:
MyObject
-> SettingsObject
-> QObject
; andsuper().__init__
, once, not each of the superclass implementations separately.As an alternative, have you considered composition? Perhaps MyObject
should take the settings:
class MyObject(QObject):
def __init__(self, param, settings=None):
super().__init__()
self.param = param
self.settings = settings
obj = MyObject('something', Settings(__file__))
Now you call self.settings.method(...)
instead of self.method(...)
, but you don't have the complexity that multiple inheritance introduces.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With