I have this code:
class Person:
def __init__(self, name, last_name, age):
self.name = name
self.last_name = last_name
self.age = age
class Student(Person):
def __init__(self, name, last_name, age, indexNr, notes):
super().__init__(name, last_name, age)
self.indexNr = indexNr
self.notes = notes
class Employee(Person):
def __init__(self, name, last_name, age, salary, position):
super().__init__(name, last_name, age)
self.salary = salary
self.position = position
class WorkingStudent(Student, Employee):
def __init__(self, name, last_name, age, indexNr, notes, salary, position):
Student.__init__(name, last_name, age, indexNr, notes)
Employee.__init__(name, last_name, age, salary, position)
I want to create a WorkingStudent instance like this:
ws = WorkingStudent("john", "brown", 18, 1, [1,2,3], 1000, 'Programmer')
but it's not working, I get this error:
TypeError: __init__() missing 1 required positional argument: 'notes'
Or what I am doing wrong here? Also, I have already tried super()
in WorkingStudent class but it calls only the constructor of the first passed class. i.e in this case Student
Note: I have already gone through multiple StackOverflow queries but I couldn't find anything that could answer this. (or maybe I have missed).
CodePudding user response:
Instead of explicit classes, use super()
to pass arguments along the mro:
class Person:
def __init__(self, name, last_name, age):
self.name = name
self.last_name = last_name
self.age = age
class Student(Person):
def __init__(self, name, last_name, age, indexNr, notes, salary, position):
# since Employee comes after Student in the mro, pass its arguments using super
super().__init__(name, last_name, age, salary, position)
self.indexNr = indexNr
self.notes = notes
class Employee(Person):
def __init__(self, name, last_name, age, salary, position):
super().__init__(name, last_name, age)
self.salary = salary
self.position = position
class WorkingStudent(Student, Employee):
def __init__(self, name, last_name, age, indexNr, notes, salary, position):
# pass all arguments along the mro
super().__init__(name, last_name, age, indexNr, notes, salary, position)
# uses positional arguments
ws = WorkingStudent("john", "brown", 18, 1, [1,2,3], 1000, 'Programmer')
# then you can print stuff like
print(f"My name is {ws.name} {ws.last_name}. I'm a {ws.position} and I'm {ws.age} years old.")
# My name is john brown. I'm a Programmer and I'm 18 years old.
Check mro:
WorkingStudent.__mro__
(__main__.WorkingStudent,
__main__.Student,
__main__.Employee,
__main__.Person,
object)
When you create an instance of WorkingStudent, it's better if you pass keyword arguments so that you don't have to worry about messing up the order of arguments.
Since WorkingStudent defers the definition of attributes to parent classes, immediately pass all arguments up the hierarchy using super().__init__(**kwargs)
since a child class doesn't need to know about the parameters it doesn't handle. The first parent class is Student, so self.IndexNr etc are defined there. The next parent class in the mro is Employee, so from Student, pass the remaining keyword arguments to it, using super().__init__(**kwargs)
yet again. From Employee, define the attributes defined there and pass the rest along the mro (to Person) via super().__init__(**kwargs)
yet again.
class Person:
def __init__(self, name, last_name, age):
self.name = name
self.last_name = last_name
self.age = age
class Student(Person):
def __init__(self, indexNr, notes, **kwargs):
# since Employee comes after Student in the mro, pass its arguments using super
super().__init__(**kwargs)
self.indexNr = indexNr
self.notes = notes
class Employee(Person):
def __init__(self, salary, position, **kwargs):
super().__init__(**kwargs)
self.salary = salary
self.position = position
class WorkingStudent(Student, Employee):
def __init__(self, **kwargs):
# pass all arguments along the mro
super().__init__(**kwargs)
# keyword arguments (not positional arguments like the case above)
ws = WorkingStudent(name="john", last_name="brown", age=18, indexNr=1, notes=[1,2,3], salary=1000, position='Programmer')
CodePudding user response:
Problem: we have a lot of arguments for our most derived class, that need to be used to initialize all the bases. However, in Python's multiple inheritance system with super()
, the class that will be initialized next depends on an MRO (method resolution order) that may have been determined in another class. Therefore, when we use multiple inheritance, we don't know which class will have its __init__
called when we use super()
.
Solution: use consistent names for the parameters, and then take advantage of **kwargs
, so that each class takes in the (explicitly named) parameters it cares about, and forwards the rest.
That looks like:
class Person:
def __init__(self, name, last_name, age):
self.name = name
self.last_name = last_name
self.age = age
class Student(Person):
def __init__(self, indexNr, notes, **kwargs):
super().__init__(**kwargs)
self.indexNr = indexNr
self.notes = notes
class Employee(Person):
def __init__(self, salary, position, **kwargs):
super().__init__(**kwargs)
self.salary = salary
self.position = position
class WorkingStudent(Student, Employee):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Forcing the client code to use keyword arguments is more work for the clients, but it also helps guard against errors from mistaking the order of positional arguments.
CodePudding user response:
This seems to work, but it's not beautiful. I admit that I'm not familiar with multiple inheritance in Python, and I think it may not be the most correct way. Hope someone else can give a better answer.
class Person:
def __init__(self, name, last_name, age):
self.name = name
self.last_name = last_name
self.age = age
class Student(Person):
def __init__(self, name, last_name, age, indexNr, notes):
Person.__init__(self, name, last_name, age)
self.indexNr = indexNr
self.notes = notes
class Employee(Person):
def __init__(self, name, last_name, age, salary, position):
Person.__init__(self, name, last_name, age)
self.salary = salary
self.position = position
class WorkingStudent(Student, Employee):
def __init__(self, name, last_name, age, indexNr, notes, salary, position):
Student.__init__(self, name, last_name, age, indexNr, notes)
Employee.__init__(self, name, last_name, age, salary, position)
ws = WorkingStudent("john", "brown", 18, 1, [1, 2, 3], 1000, 'Programmer')
print(ws)
Output:
<__main__.WorkingStudent object at 0x000002526F11F3D0>
CodePudding user response:
This is the easiest way to reach your goal.
If your WorkingStudent class inherite Student and Employee class like this ,
class WorkingStudent(Student, Employee):
def __init__(self, name, last_name, age, indexNr, notes, salary, position):
Student.__init__(self, name, last_name, age, indexNr, notes)
Employee.__init__(self, name, last_name, age, salary, position)
ws = WorkingStudent("john", "brown", 18, 1, [1, 2, 3], 1000, 'Programmer')
print(ws)
print(ws.name)
print(ws.age)
your output will be...
Output:
<main.WorkingStudent object at 0x7fc6c4d8ba10>
john
18
[1, 2, 3]