I’m trying to learn and understand how to use super in Python, Ive been following the book ‘Python journey from novice to expert’ and although I feel that I understand the concept Im having problems executing super in my own code.
For example, this method works for me:
class Employee: def __init__(self, firstname, lastname, age, sex, dob): self.firstname = firstname self.lastname = lastname self.age = age self.sex = sex self.dob = dob self.all_staff.append(self) class Hourly(Employee): def __init__(self, firstname, lastname, age, sex, dob, rate, hours): self.rate = rate self.hours = hours super().__init__(firstname, lastname, age, sex, dob) def __str__(self): return "{} {}nAge: {}nSex: {}nDOB: {}n".format(self.firstname, self.lastname, self.age, self.sex, self.dob) def get_rate(self): print('The hourly rate of {} is {} '.format(self.firstname, self.rate)) hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30') print(hourlystaff1) print(hourlystaff1.get_rate())
returns the following:
Bob Foo Age: 23 Sex: M DOB: 12/1/1980 The hourly rate of Bob is $15 None
This is what I expected (I’m not sure why ‘None’ is also being returned though, perhaps someone can explain?).
And then I wanted to try this using super but with **kwargs like so:
class Employee: def __init__(self, firstname='', lastname='', age='', dob='', **kwargs): super().__init__(**kwargs) self.firstname = firstname self.lastname = lastname self.age = age self.dob = dob class Hourly(Employee): def __init__(self, rate=''): self.rate = rate super().__init__(**kwargs) def __str__(self): return "{} {}nAge: {}nSex: {}".format(self.firstname, self.lastname, self.age, self.sex, self.dob, self.rate) def get_rate(self): print('The hourly rate of {} is {} '.format(self.firstname, self.rate)) bob = Hourly('Bob', 'Bar', '23', '12/1/2019') bob.get_rate('$12')
returns this error:
File "staff_b.py", line 33, in <module> bob = Hourly('Bob', 'Bar', '23', '12/1/2019') TypeError: __init__() takes from 1 to 2 positional arguments but 5 were given
what am I doing wrong in this second approach? How can I use **kwargs and super correctly here?
Edit:
this is a screenshot of an example from the book which I have been following:
what is different between how I use **kwargs and super in my second example to there?
This is also a comprehensive case study from the same book and chapter. This works for me and I understand how it works but I dont seem to be able to translate it into my own work.
Advertisement
Answer
The poblem you have here isn’t really specific to super but more specific to kwargs. If we throw most of your code away and remove the super it looks like this:
class Hourly(Employee): def __init__(self, rate=''): self.rate = rate some_crazy_function(**kwargs) hourlystaff1 = Hourly('Bob', 'Foo', '23', 'M', '12/1/1980', '$15', '30')
There are two obvious problems: The __init__
function is getting more arguments passed than expected and in the body of the __init__
function is a reference to kwargs
which is not defined anywhere. While here understanding **kwargs
(and its sibling *args
) is enough to fix the problem here super and **kwargs
are very useful together. Lets first look why super
is useful. Lets imagine we write some wrappers around subprocesses with some nice helper methods (the architecture is maybe not the best fit for the problem, but only ever seeing animals with inheritance is also not super helpful. Multiple inheritance is a really rare case, so it’s hard to come up with good examples that are not Animals, GameEntities or GUIwidgets):
class Process: def __init__(self, exe): self.exe = exe self.run() class DownloadExecutableBeforeProcess(Process): def __init__(self, exe): self.download_exe(exe) Process.__init__(self, exe)
Here we are doing inheritance and we do not even need to use super – we can just use the name of the superclass explicitly and have the behavior we want. We could rewrite to use super
here but it would not change the behavior. If you only inherit from one class you don’t strictly need super
, although it can help you to not repeat the classname you inherit from. Lets add to our class hirarchy and include inherting from more than one class:
class AuthenticationCheckerProcess(Process): def __init__(self, exe, use_sha=True): self.check_if_authorized(exe, use_sha) Process.__init__(self, exe) class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess): def __init__(self, exe): DownloadExecutableBefore.__init__(exe) AuthenticationCheckerProcess.__init__(exe, use_sha=False)
If we follow the init of DownloadAndCheck
we see that Process.__init__
is called twice, once through DownloadExecutableBefore.__init__
and once through AuthenticationCheckerProcess.__init__
! So our process we want to wrap is also run twice, which is not what we want. Here in this example we could fix this easily by not calling self.run()
in the init of process, but in realworld cases this is not always so easy to fix like here. Calling Process.__init__
just seems wrong in this case. Can we somehow fix this?
class DownloadAndCheck(DownloadExecutableBefore, AuthenticationCheckerProcess): def __init__(self, exe): super().__init__(exe, use_sha=False) # also replace the Process.__init__ cals in the other classes with super
super
fixes this problems and will only call Process.__init__
once. It will also take care of the order in which the function should run, but this is not a big problem here. We still have a problem: use_sha=False
will get passed to all initializers, but only one actually needs it. We can’t really only pass the variable to only the functions that need it (because figuring that out would be a nightmare) but we can teach the other __init__
s to just ignore the keywoard:
class Process: def __init__(self, exe, **kwargs): # accept arbitrary keywoards but ignore everything but exe # also put **kwargs in all other initializers self.exe = exe self.run() class DownloadExecutableBeforeProcess(Process): def __init__(self, exe, **kwargs): self.download_exe(exe) # pass the keywoards into super so that other __init__s can use them Process.__init__(self, exe, **kwargs)
Now the super().__init__(exe, use_sha=False)
call will succeed, each initializer only takes the keywoards it understands and simply passes the others further down.
So if you have mutliple inheritance and use different (keywoard) arguments super and kwargs can solve your problem. But super and multiple inheritance is complicated, especially if you have more inheritance layers than here. Sometimes the order in which functions should be calles is not even defined (and python should throw an error then, see e.g. explenation of change of MRO algorithm). Mixins might even require a super().__init__()
call although they don’t even inherit from any class. All in all you gain a lot of complexity in your code if you use multiple inheritance, so if you don’t really need it, it’s often better to think of other ways to model your problem.