Inheritance in Python
By the Syncfusion DevTutorials team · Last updated
Parent and child classes, the five types of inheritance, inheriting and overriding, super(), and isinstance() and issubclass().
Everyone at a school is a person. A person has a name and an age. A student is a person with marks and a class. A teacher is a person with a subject and a salary. A principal is a teacher with extra responsibilities.
You could copy the code for a name and age into each class. Instead, write that shared code once in Person and let the other classes build on it. Inheritance is how one class reuses and extends another class.
Said once, used by both
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I am {self.name}, {self.age} years old"
class Student(Person):
pass
class Teacher(Person):
pass
print(Student("Asha", 13).introduce())
print(Teacher("Meera", 34).introduce())
Student and Teacher do not define any methods of their own here. They can still use Person's __init__ and introduce() because they inherit them.
Inheritance lets one class reuse the attributes and methods of another class, then add or change behaviour.
What You Will Learn
In this lesson, you will learn:
- What inheritance is, and the words used to describe it
- The five shapes inheritance can take
- How a child class adds to and reuses what it inherited
- How
super()calls a method in the inheritance search order - How to check an object's type and how classes relate
Inheritance
What Is Inheritance?
Inheritance lets you define one class using another class as a starting point. The new class can use the parent's methods and attributes, then add its own or replace a method with a different version.
Parent and Child Classes
The class being used as a starting point is the parent, also called the base class or superclass. The new class is the child, also called the derived class or subclass.
class Child(Parent):
A child adding something of its own
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
class Student(Person):
def study(self):
return f"{self.name} is studying"
asha = Student("Asha")
print(asha.introduce())
print(asha.study())
Student defines one method, study(). It can also use introduce() from Person, so the object has both methods.
Benefits of Inheritance
Without inheritance, the same behaviour may need to be written in more than one class:
The same code written twice
class Student:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I am {self.name}, {self.age} years old"
class Teacher:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I am {self.name}, {self.age} years old"
print(Student("Asha", 13).introduce())
print(Teacher("Meera", 34).introduce())
Both examples work. If you change how introduce() is written, you must update both classes. With more classes, it is easy to miss one.
| Benefit | What it means |
|---|---|
| One place to change | shared behaviour lives in the parent |
| Less to read | a child shows only what is different |
| A shared type | code that accepts a Person can also accept a Student or Teacher |
| Easy to extend | a new kind of person can reuse the code that already exists |
Code Reusability
When deciding whether inheritance fits, ask: is one thing a kind of the other? A student is a person, so Student can inherit from Person. A student has marks, so marks belong as an attribute, not as a parent class.
Three kinds of person, one shared base
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I am {self.name}, {self.age}"
class Student(Person):
def role(self):
return "studies here"
class Teacher(Person):
def role(self):
return "teaches here"
class Visitor(Person):
def role(self):
return "is visiting"
for person in [Student("Asha", 13), Teacher("Meera", 34), Visitor("Ravi", 40)]:
print(person.introduce(), "-", person.role())
The loop can call introduce() on all three objects because each is a kind of Person. Each class also supplies its own role() method.
Use inheritance for an "is a" relationship, not a "has a" or "uses a" relationship. A Car is a Vehicle, so inheritance fits. A Car has an Engine, so the engine should be stored as an attribute instead of making Car inherit from Engine.
Types of Inheritance
Single Inheritance
Single inheritance has one parent class and one child class. It is the simplest and most common form.
Single inheritance
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def study(self):
return f"{self.name} is studying"
print(Student("Asha").study())
Multiple Inheritance
Multiple inheritance means a class has more than one parent.
Two parents
class Teacher:
def teach(self):
return "teaching a class"
class Researcher:
def research(self):
return "writing a paper"
class Professor(Teacher, Researcher):
pass
prof = Professor()
print(prof.teach())
print(prof.research())
If both parents define a method with the same name, Python searches them in a specific order. In this example, Teacher comes first:
The order in the brackets decides
class Teacher:
def describe(self):
return "I teach"
class Researcher:
def describe(self):
return "I research"
class Professor(Teacher, Researcher):
pass
class Academic(Researcher, Teacher):
pass
print(Professor().describe())
print(Academic().describe())
The two classes have the same parents in a different order, so they find different describe() methods first. Python uses a search order called the method resolution order, or MRO. You can inspect it:
The order Python will search
class Teacher:
def describe(self):
return "I teach"
class Researcher:
def describe(self):
return "I research"
class Professor(Teacher, Researcher):
pass
print([cls.__name__ for cls in Professor.__mro__])
For this example, Python searches Professor, then Teacher, then Researcher, then object. It uses the first class in that order that defines the method.
Multiple inheritance can make the search harder to follow, so use it only when a class genuinely needs to inherit from more than one parent.
Multilevel Inheritance
Multilevel inheritance is a chain: one child inherits from a class that is itself a child.
Three levels
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
class Teacher(Person):
def teach(self):
return f"{self.name} teaches"
class Principal(Teacher):
def manage(self):
return f"{self.name} runs the school"
head = Principal("Meera")
print(head.introduce())
print(head.teach())
print(head.manage())
Principal inherits from Teacher, and Teacher inherits from Person. That is why head can use all three methods.
Hierarchical Inheritance
Hierarchical inheritance has one parent and several child classes. Each child can reuse the same parent behaviour.
Several children, one parent
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
class Student(Person):
pass
class Teacher(Person):
pass
class Cleaner(Person):
pass
for person in [Student("Asha"), Teacher("Meera"), Cleaner("Ravi")]:
print(type(person).__name__, "->", person.introduce())
Hybrid Inheritance
Hybrid inheritance combines more than one of the shapes above. Here, Student and Teacher both inherit from Person, and TeachingAssistant inherits from both:
Person
├─ Student ─┐
└─ Teacher ─┴─ TeachingAssistant
A hierarchy that joins back together
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def study(self):
return f"{self.name} studies"
class Teacher(Person):
def teach(self):
return f"{self.name} teaches"
class TeachingAssistant(Student, Teacher):
pass
ta = TeachingAssistant("Asha")
print(ta.study())
print(ta.teach())
print([cls.__name__ for cls in TeachingAssistant.__mro__])
Person appears once in the search order even though both parents inherit from it. This is called the diamond shape. Python's method resolution order decides where it looks for methods.
| Type | Shape | Example |
|---|---|---|
| Single | one parent, one child | Student(Person) |
| Multiple | two or more parents | Professor(Teacher, Researcher) |
| Multilevel | a chain | Principal(Teacher(Person)) |
| Hierarchical | one parent, several children | Student, Teacher, Cleaner from Person |
| Hybrid | a mixture | TeachingAssistant(Student, Teacher) |
Working with Inheritance
Inheriting Attributes
A child class can use class attributes from its parent. When the parent's constructor runs, it can also set instance attributes on the child object.
The parent's constructor does the work
class Person:
species = "human"
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
pass
asha = Student("Asha", 13)
print(asha.name, asha.age, asha.species)
print(vars(asha))
Student does not define its own constructor, so Python finds and uses the inherited Person.__init__(). The species class attribute is also available through the student object.
Inheriting Methods
Methods come down the chain too
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
def greet(self, other):
return f"{self.name} greets {other.name}"
class Student(Person):
pass
asha = Student("Asha")
ravi = Student("Ravi")
print(asha.introduce())
print(asha.greet(ravi))
Calling Parent Methods
In a simple parent-child relationship, when a child defines its own constructor but also needs the parent's setup, super() calls the parent's constructor.
super() runs the parent's constructor
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, marks):
super().__init__(name, age)
self.marks = marks
asha = Student("Asha", 13, 78)
print(vars(asha))
super().__init__(name, age) lets Person set the name and age. Then Student adds the marks.
If the child does not call the parent's constructor, the attributes that constructor normally sets will be missing:
Forgetting to call the parent's constructor
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, name, age, marks):
self.marks = marks
asha = Student("Asha", 13, 78)
print(asha.marks)
print(asha.name)
marks was set, but name was not, because Person.__init__() never ran.
super() can also call a method other than the constructor:
Adding to the parent's answer
class Person:
def __init__(self, name):
self.name = name
def introduce(self):
return f"I am {self.name}"
class Student(Person):
def __init__(self, name, marks):
super().__init__(name)
self.marks = marks
def introduce(self):
return super().introduce() + f", and I scored {self.marks}"
print(Student("Asha", 78).introduce())
The child calls the parent's introduce() and adds the student's marks to the returned sentence. This reuses the parent's work instead of copying the whole method.
Extending Existing Classes
A child class can also add behaviour to a built-in class.
Building on a built-in
class MarkSheet(list):
def average(self):
return round(sum(self) / len(self), 1)
def passed(self):
return [mark for mark in self if mark >= 35]
marks = MarkSheet([78, 20, 85, 60])
print(marks)
print(len(marks))
print(marks.average())
print(marks.passed())
marks.append(92)
print(marks)
MarkSheet is a list with two extra methods. It also keeps the usual list operations, such as len() and append(). The average() example uses a list with at least one mark.
In a simple parent-child relationship, super() lets each class handle its own part. The parent sets the values it knows about, and the child adds its own values without repeating the parent's setup.
Relationship Functions
isinstance()
isinstance(object, Class) checks whether an object was made from that class or from a child class of it.
Asking what an object is
class Person:
pass
class Student(Person):
pass
asha = Student()
print(isinstance(asha, Student))
print(isinstance(asha, Person))
print(isinstance(asha, object))
A Student is also a Person, so both checks are True. Every Python class also inherits from the built-in object class.
type() answers a different question: it gives the exact class used to make the object.
type() is exact, isinstance() is not
class Person:
pass
class Student(Person):
pass
asha = Student()
print(type(asha) is Student)
print(type(asha) is Person)
print(isinstance(asha, Person))
Use isinstance() when you want to accept an object of a class or any of its child classes. type(x) is C only accepts objects made directly from exactly C, not its child classes.
You can give isinstance() a tuple of classes to check more than one kind at once:
Checking against several classes
class Person:
pass
class Student(Person):
pass
class Teacher(Person):
pass
for person in [Student(), Teacher()]:
print(type(person).__name__, isinstance(person, (Student, Teacher)))
issubclass()
issubclass() checks how two classes are related. It asks whether the first class is the same as, or a child of, the second class.
Asking how classes relate
class Person:
pass
class Student(Person):
pass
print(issubclass(Student, Person))
print(issubclass(Person, Student))
print(issubclass(Student, object))
print(issubclass(Student, Student))
A class counts as its own subclass, so the last check is also True.
Object Relationship Checking
These checks are useful when a function needs to handle different kinds of objects in different ways.
One function, several kinds
class Person:
def __init__(self, name):
self.name = name
class Student(Person):
def __init__(self, name, marks):
super().__init__(name)
self.marks = marks
class Teacher(Person):
def __init__(self, name, subject):
super().__init__(name)
self.subject = subject
def describe(person):
if not isinstance(person, Person):
return "not a person at all"
if isinstance(person, Student):
return f"{person.name} is a student scoring {person.marks}"
if isinstance(person, Teacher):
return f"{person.name} teaches {person.subject}"
return f"{person.name} is some other kind of person"
print(describe(Student("Asha", 78)))
print(describe(Teacher("Meera", "Maths")))
print(describe(Person("Ravi")))
print(describe("just some text"))
The order matters: every Student and Teacher is also a Person, so check those child classes before the general Person case. This is like putting more specific conditions before a broader one in an if chain.
Every class also has information about its direct parent classes and its full method search order:
Looking at the family tree
class Person:
pass
class Teacher(Person):
pass
class Principal(Teacher):
pass
print(Principal.__bases__)
print([cls.__name__ for cls in Principal.__mro__])
Common mistakes
Each row shows a mistake, what appears, why it happens, and how to fix it.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| A child constructor that never calls the parent's | AttributeError for an attribute the parent sets | The parent's __init__ never ran | Call super().__init__(...) |
Calling super().__init__() with the wrong arguments | TypeError: missing 1 required positional argument | The parent still needs its own values | Pass the values the parent expects |
| Using inheritance for "has a" | A class with methods that do not belong to it | Inheritance means "is a" | Store the other object as an attribute |
type(x) is Person to accept subclasses | False for a Student | type() checks only the exact class | Use isinstance(x, Person) |
| Checking the widest class first | Every object takes the first branch | A Student is also a Person | Check child classes first |
| Expecting the second parent's method to win | The first parent's version runs | The MRO checks the first parent first in this example | Check __mro__ |
| Overriding a method and forgetting the parent's work | Part of the original behaviour is missing | The child replaced the method instead of extending it | Call super().method() inside |
| Giving a child and parent attribute the same name | The child's assignment replaces that value on the object | Both assignments use the same attribute name | Use a distinct name unless replacing the value is intended |
| Deep inheritance chains | It is hard to find where a method comes from | Many levels make the search harder to follow | Prefer shallow trees and attributes |
What to Remember
- Inheritance lets one class reuse and extend another class, without copying its methods.
- The class being inherited from is the parent; the new class is the child.
- Use inheritance for "is a" relationships. Use an attribute for "has a" relationships.
- The five shapes are single, multiple, multilevel, hierarchical and hybrid.
- With multiple parents, Python follows the method resolution order to find a method.
__mro__shows that order. - A child with no
__init__can use the parent's. A child that defines its own constructor must callsuper().__init__(...)if it needs the parent's setup. - In a simple parent-child relationship,
super()lets a child add to the parent's method instead of copying it. isinstance(obj, Class)accepts instances of that class and its child classes.type(x) is Cchecks for the exact class.issubclass(A, B)asks whether one class is the same as, or inherits from, another.- When checking object types in a chain, check the most specific classes first.
Next: Polymorphism