How to create instance of multiple inherited class? - python

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).

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')

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.

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]

Related

How to inherit from 2 classes without having to change the original classes?

Whenever I run this code I always get a TypeError that says __init__() missing 1 required positional argument: 'hours',
but I am not trying to change anything from the original class that the ScientificSwimmer class is inheriting from…if that makes sense.
Here is the code:
class Human:
def __init__(self, name, age):
self.name = name
self.age = age
def hobby(self):
print("Likes watching Netflix")
def info(self):
print(self.name , "is ",self.age," years old")
class Scientist(Human):
def __init__(self, name, age, lab):
super().__init__(name, age)
self.lab = lab
def hobby(self):
print("Likes doing scientific experiments")
def labName(self,lab):
print("works at the " ,lab, "laboratory")
class Swimmer(Human):
def __init__(self, name, age, hours):
super().__init__(name,age)
self.hours = hours
def hobby(self):
print("likes swimmming in the lake" )
def hoursSwimming(self, hours):
print("swims ", hours , "hours per week")
class ScientificSwimmer(Scientist, Swimmer):
def __init__ (self, name, age, lab, hours):
Scientist.__init__(self,name, age, lab)
Swimmer.__init__(self, name, age, hours)
scienswim = ScientificSwimmer("\nJohn Smith", 30, "nuclear", 100)
scienswim.info()
scienswim.hobby()
scienswim.labName("nuclear")
scienswim.hoursSwimming(100)
My desired result is for it to print:
John smith is 30 years old.
likes doing scientific experiments.
works at the nuclear laboratory.
swims 100 hours per week.
Your original classes simply aren't designed correctly to support cooperative inheritance, which is what super is designed for. The number one rule for using super is this: you can't assume you know which class super() will refer to. That's determined by the runtime type of self, not the classes you statically inherit from.
Use keyword arguments exclusively to ensure that the method signatures remain compatible, and always call super().__init__ with any unknown keyword arguments (namely, those not explicitly mentioned in the signature).
class Human:
def __init__(self, *, name, age, **kwargs):
super().__init__(**kwargs)
self.name = name
self.age = age
def hobby(self):
print("Likes watching Netflix")
def info(self):
print(self.name , "is ",self.age," years old")
class Scientist(Human):
def __init__(self, *, lab, **kwargs):
super().__init__(**kwargs)
self.lab = lab
def hobby(self):
print("Likes doing scientific experiments")
def labName(self):
print("works at the " , self.lab, "laboratory")
class Swimmer(Human):
def __init__(self, *, hours, **kwargs):
super().__init__(**kwargs)
self.hours = hours
def hobby(self):
print("likes swimmming in the lake" )
def hoursSwimming(self):
print("swims ", self.hours , "hours per week")
class ScientificSwimmer(Scientist, Swimmer):
pass
scienswim = ScientificSwimmer(name="John Smith", age=30, lab="nuclear", hours=100)
scienswim.info()
scienswim.hobby()
scienswim.labName()
scienswim.hoursSwimming()
The method resolution order for ScientificSwimmer is [ScientificSwimmer, Scientist, Swimmer, Human, object]. This results in the following:
ScientificSwimmer need not be defined, as the other __init__ methods will take care of everything.
Scientist.__init__ extracts the lab keyword argument and passes the rest on to the next class.
Swimmer.__init__ extracts the hours keyword argument and passes the rest on to the next class.
Human.__init__ extracts the name and age keyword arguments and passes the rest on to the next class.
object.__init__ receives no keyword arguments, all defined arguments having been correctly extracted by downstream classes.

Python Inheritence from constructor

person.py
class Person:
"""---A class representing a person---"""
# Person constructor
def __init__(self,n,a):
self.full_name = n
self.age = a
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
Person.__init__(self,n,a)
self.school = s
driver.py
from person import *
a = Student("Alice", 19, "Univ")
It throws TypeError: __init__() takes 3 positional arguments but 4 were given
I tried to change Student class to the following:
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
super().__init__(n,a)
self.school = s
The error still exists.
Why does this happen? Is super() keyword required to add new attributes?
EDIT: The problem is solved. There was an indentation issue in the source code rendering this strange behavior, hence the question should be closed.
This line:
Person.__init__(self,n,a)
Is the problem. Recall that methods are automatically passed a reference to themselves, so you just passed a second one.
There's also a well-established pattern for this:
class Person
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, school, *args):
super().__init__(*args)
self.school = school
student = Student('Washington Elementary', "Johnny Go'gettem", 10)
although note that simply removing your reference to self in the Person.__init__ call inside Student.__init__ would be sufficient.
Note that you can override the default method behavior with a couple of decorators that become quite useful in certain situations. Neither apply here, but just a bit of knowledge to tease your brain a bit:
def SomeClass:
attr = "class-scoped"
def __init__(self):
self.attr = "instance-scoped"
def some_method(self):
return self.attr == "instance-scoped"
#classmethod
def some_classmethod(cls):
return cls.attr == "class-scoped"
#staticmethod
def some_staticmethod():
return "I'm not given a \"self\" parameter at all!"
classmethods are particularly useful as alternate constructors
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#classmethod
def from_tuple(cls, tup) -> "Person":
"""Expects a tuple of (name, age) and constructs a Person"""
name, age = tup
return cls(name, age)
#classmethod
def from_dict(cls, dct) -> "Person":
"""Expects a dictionary with keys "name" and "age" and constructs a Person"""
try:
name = dct['name']
age = dct['age']
except KeyError:
raise ValueError(f"Dictionary {dct} does not have required keys 'name' and 'age'")
else:
return cls(name, age)

Super() doesn't keep the parent's class info

Trying to understand super() I made these two examples but they return the same results.
This is with super()
class Person1():
def __init__(self, name):
self.name = name
class EmailPerson1(Person1):
def __init__(self, name, email):
super().__init__(name)
self.email = email
bob2 = Person('Dim')
bob = EmailPerson1('Bob Frapples', 'bob#frapples.com')
bob.name
'Bob Frapples'
and this without super()
class Person():
def __init__(self, name):
self.name = name
class EmailPerson(Person):
def __init__(self, name, email):
self.name = name
self.email = email
bob2 = Person('Dim')
bob1 = EmailPerson('Bob Frapples', 'bob#frapples.com')
bob1.name
'Bob Frapples'
What is the difference? The first version should use the name from the parent class I think.
The difference is you are manually duplicating the work done by Person.__init__ in EmailPerson.__init__, instead of using super().__init__ to make sure the inherited initializer does what it needs to.
class Person():
def __init__(self, name):
self.name = name
class EmailPerson(Person):
def __init__(self, name, email):
self.name = name
self.email = email
Using super, i.e.,
class EmailPerson(Person):
def __init__(self, name, email):
super().__init__(name)
self.email = email
means that if someone updated the definition of Person to do some additional work in Person.__init__:
class Person:
def __init__(self, name):
self.name = name.title() # bob grapples -> Bob Frapples
you don't need to update the definition of EmailPerson at all to take advantage of the new change.

Python 3 OOP - where does "super().__init__()" go exactly?

I am trying to make my sub-class work, but something is wrong with the inheritance. The number of args passed in should be 5: name, age, gender, title & salary
However, Python is saying
TypeError: __init__() takes 4 positional arguments but 6 were given
and I don't know why or how to fix it. Here is my code:
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Employee(Person):
def emp_function(self, title, salary):
self.title = title
self.salary = salary
super().__init__()
#Is this wrong? Where should this 'super()' go?
George = Employee("George", 30, "male", "Manager", 50000)
super().__init__() should be in def __init__ of Employee class
You need to create constructor for Employee class too.
class Person:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Employee(Person):
def __init__(self,name,age,gender,title,salary):
self.title = title
self.salary = salary
super().__init__(name, age, gender)
George = Employee("George", 30, "male", "Manager", 50000)

Trying to create a object from a class by calling a function

I am working on a project that I need to create some objects dynamically from a class. I am not asking for anyone to solve it but just point me in the right direction. An example would be if I was working with Dogs.
class Dog(object):
"""Creating Dogs"""
def __init__(self, name, age, gender):
super(Dog, self).__init__()
self.name = name
self.name = age
self.name = gender
I would like however to have a function that I can pass this information into that would create a global object. The function would look like this:
def create_dog(name, age, gender):
name = Dog(name,age, gender)
return(name)
So theoretically if I passed the name "Jack" to the function I would have a object globally called "Jack" from the class. I am just trying to find a good way to do this if any. If this is completely against python I will figure out another way but I am sitting here stuck. I have also thought about nesting the class within my function but the namespace won't go into global.
you could create a global dictionary
dogs = {}
def create_dog(name, age, gender):
# maybe check if already exists
dogs[name] = Dog(name, age, gender)
With changing locals: (which is still bad, but not as much as changing globals)
def create_dog(name, age, gender, locals_=locals()):
dog = Dog(name, age, gender)
locals_[name] = dog
create_dog("jack", 3, "male")
print(jack)
Maybe you want to make an alternative constructor ?
class Dog(object):
"""Creating Dogs"""
def __init__(self, name, age, gender):
super(Dog, self).__init__()
self.name = name
self.name = age
self.name = gender
#classmethod
def create_dog(name, age, gender):
name = Dog(name,age, gender)
return name
See also discussion Meaning of #classmethod and #staticmethod for beginner?
A global dictionary is fine if you use a single file script, but for a more complex software you'll have to import all the 3 elements:
from dogs import dogs, create_dog, Dog
With class attribute you import just one name space. So you could do this:
class Dog(object):
"""Creating Dogs"""
members = []
def __init__(self, name, age, gender):
super(Dog, self).__init__()
self.name = name
self.name = age
self.name = gender
#classmethod
def create_dog(name, age, gender):
name = Dog(name,age, gender)
Dog.members.append(name)
return name
Now you can simply import Dog, and access the class attribute Dog.members

Categories

Resources