Multiple Inheritance
On this page
Example: Teaching AssistantA class can inherit from more than one base. Python uses a method resolution order (MRO) to decide which implementation a lookup finds.
The UML hollow triangles show the two base classes. Python’s lookup order is stated in the note; it is not encoded by the generalization lines themselves.
class Base1:
def greet(self):
return "Hello from Base1"
class Base2:
def greet(self):
return "Hello from Base2"
class Derived(Base1, Base2):
pass
d = Derived()
print(d.greet())Output
Hello from Base1
Base1 precedes Base2, so its greet wins. Python calculates a C3 ordering, not a simple depth-first traversal; inspect Derived.__mro__ when needed.
Example: Teaching Assistant
The teaching assistant combines Student behavior with Employee information. Read the definitions, then run the example below.
The UML generalization triangles point to base classes. In this implementation, Student inherits from User, while Employee is a separate base.
class User:
def __init__(self, user_id, name, email):
self.user_id = str(user_id)
self.name = name
self.email = email
def profile(self):
return f"{self.name} <{self.email}>"
def role(self):
return "user" # default label
def describe(self):
return f"{self.role().title()}: {self.profile()}"
class Student(User):
def __init__(self, user_id, name, email):
super().__init__(user_id, name, email)
self.courses = [] # student-specific attribute
def role(self):
return "student" # override the parent's version
def enroll(self, course_code):
if course_code not in self.courses:
self.courses.append(course_code)
def describe(self):
base = super().describe() # reuse User's describe() output
return f"{base} — enrolled in {len(self.courses)} course(s)"
class Professor(User):
def __init__(self, user_id, name, email):
super().__init__(user_id, name, email)
self.teaches = []
def role(self):
return "professor" # override parent's version
def assign_course(self, course_code):
if course_code not in self.teaches:
self.teaches.append(course_code)
def describe(self):
base = super().describe()
return f"{base} — teaches {len(self.teaches)} course(s)"
class Employee:
def __init__(self, employee_id):
self.employee_id = employee_id
def pay_info(self):
return f"Employee #{self.employee_id}"
class TeachingAssistant(Student, Employee):
def __init__(self, user_id, name, email, employee_id):
Student.__init__(self, user_id, name, email) # initialize Student part
Employee.__init__(self, employee_id) # initialize Employee part
self.assists = []
def role(self):
return "teaching assistant"
def assist(self, course_code):
if course_code not in self.assists:
self.assists.append(course_code)Defines User, Student, Professor, Employee, TeachingAssistant; no visible output.
Student initializes User; TeachingAssistant separately initializes Employee. This works for this specific hierarchy. Do not generalize explicit base calls to cooperative diamond inheritance, where a shared base might otherwise be initialized twice.
ta = TeachingAssistant("S-010", "Alan Turing", "alan@example.org", "E-500")
ta.enroll("CS101")
ta.assist("CS101")
print(ta.describe()) # from Student/User
print(ta.pay_info()) # from EmployeeOutput
Teaching Assistant: Alan Turing <alan@example.org> — enrolled in 1 course(s)
Employee #E-500
The inherited description uses the overridden role; pay_info reads the employee identifier.
Try it: reverse Base1 and Base2 in the first example. Predict the greeting, run it, and inspect the MRO. Prefer a simpler composition design when separate roles do not need shared inheritance.
With these relationships established in code, we can use diagrams to make an object-oriented design easier to inspect.