Python Inheritance
Table of Contents + −
In the last lesson you learned Methods. A method lets an object do things with its own data. Now imagine you build a Dog class, a Cat class, and a Cow class. They all eat. They all sleep. They all have a name. If you copy that same code into every class, you are writing the same thing again and again. Inheritance fixes that. It lets one class borrow everything from another class. So you write the shared part only once.
🤔 Why Inheritance?
Here is the pain. You have many classes that share a lot of behaviour. A Dog, a Cat, and a Cow are all animals. They all have a name. They all eat and sleep. Without inheritance you copy eat() and sleep() into every single class. Then one day you change how eating works. Now you have to fix it in five places. Miss one, and you have a bug.
Inheritance means a class can take all the variables and methods of another class for free. Then it adds its own on top. You write the shared code once in a parent class. Every child class gets it automatically.
The relationship inheritance models is “is a”:
- A
Dogis anAnimal. - A
Catis anAnimal. - A
SavingsAccountis aBankAccount.
If you can say “X is a Y” and it sounds right, then X can inherit from Y.
🧩 The Parent and the Child
The class you borrow from is the parent class. It is also called the base class or super class. The class that borrows is the child class. It is also called the subclass or derived class.
Think of a phone. A smartphone is a phone. It already does everything a basic phone does, like calling and texting, because it is a phone. Then it adds new things on top, like apps and a camera. The smartphone did not rebuild calling from scratch. It inherited it.
Here is the syntax. You put the parent’s name in parentheses after the child’s name:
class Parent: # shared code lives here pass
class Child(Parent): # Child gets everything from Parent, plus its own extras passThat (Parent) is the whole trick. It says “Child is a Parent, give it everything Parent has.”
🐾 A Simple Example
Let’s build an Animal parent and a Dog child. The Animal class knows how to store a name. It also knows how to eat and sleep. The Dog class adds barking. But it does not repeat the eating and sleeping code.
class Animal: def __init__(self, name): self.name = name
def eat(self): print(f"{self.name} is eating.")
def sleep(self): print(f"{self.name} is sleeping.")
class Dog(Animal): def bark(self): print(f"{self.name} says Woof!")
buddy = Dog("Buddy")buddy.eat()buddy.sleep()buddy.bark()Run that and you get:
Output
Buddy is eating.Buddy is sleeping.Buddy says Woof!Look at what happened, line by line:
- We made
buddyfrom theDogclass, not theAnimalclass. - We called
buddy.eat()andbuddy.sleep(), butDognever defines those. They came fromAnimal. - We called
buddy.bark(), whichDogadds on its own.
The Dog class did not copy eat, sleep, or even __init__. It got all of them from Animal for free. That is the reuse inheritance gives you. Write it once in the parent. Then every child can use it.
🛠️ Using super() to Run the Parent Constructor
Now a real problem shows up. What if a Dog needs its own extra data at creation time, like a breed, on top of the name? You would write a new __init__ in Dog. But the moment you do that, Dog stops using the parent’s __init__. So the name setup disappears.
Here is the trap, written out:
class Animal: def __init__(self, name): self.name = name
class Dog(Animal): # ❌ Avoid: this forgets to set name def __init__(self, breed): self.breed = breed
buddy = Dog("Labrador")print(buddy.breed) # worksprint(buddy.name) # crashesTrying to read buddy.name crashes because nobody ever set it:
Output
LabradorTraceback (most recent call last): File "main.py", line 12, in <module> print(buddy.name)AttributeError: 'Dog' object has no attribute 'name'The fix is super(). It means “the parent class”. So super().__init__(name) calls the parent’s __init__ and lets it do its job of setting the name. Then the child adds its own extra data after.
Here is the corrected version:
class Animal: def __init__(self, name): self.name = name
def eat(self): print(f"{self.name} is eating.")
class Dog(Animal): def __init__(self, name, breed): # ✅ Good: let the parent set up name first super().__init__(name) self.breed = breed
def describe(self): print(f"{self.name} is a {self.breed}.")
buddy = Dog("Buddy", "Labrador")buddy.eat()buddy.describe()Run it and both pieces of data are there:
Output
Buddy is eating.Buddy is a Labrador.Reading it step by step:
Dog.__init__takes bothnameandbreed.super().__init__(name)handsnameup toAnimal.__init__, which setsself.name.- Then
Dogsetsself.breeditself. eat()still works, because it came down fromAnimal.
So super() lets the child reuse the parent’s setup instead of redoing it. The parent handles the shared part. The child handles its own part.
Tip
Call super().__init__(...) as the first line of the child’s __init__. That way the parent finishes its setup before your child code runs. Then nothing the parent created is missing.
🔁 Overriding: Changing What the Parent Does
Sometimes the child should not just add behaviour. It should change it. A Dog makes a different sound than a generic animal. So the child can define a method with the same name as the parent’s. Then the child’s version wins. This is called overriding.
class Animal: def __init__(self, name): self.name = name
def speak(self): print(f"{self.name} makes a sound.")
class Dog(Animal): def speak(self): print(f"{self.name} says Woof!")
generic = Animal("Creature")buddy = Dog("Buddy")
generic.speak()buddy.speak()The output shows each one using its own version:
Output
Creature makes a sound.Buddy says Woof!Dog still inherited speak from Animal. But because Dog defines its own speak, Python uses the Dog one for any dog. The parent’s version stays available for plain animals. This is how a child keeps most of the parent but tweaks the bits it needs.
📋 Words You’ll See
These terms all describe the two sides of inheritance. Same idea, different names.
| Term | Means |
|---|---|
| Parent / Base / Super class | The class being inherited from |
| Child / Sub / Derived class | The class that inherits |
super() | A way to reach the parent class from inside the child |
| Override | Redefine a parent method in the child |
⚠️ Common Mistakes
A few traps catch people the first time:
- Forgetting
super().__init__(). If the child writes its own__init__and never calls the parent’s, the parent’s data never gets set. Then you get anAttributeErrorlater. - Passing the wrong arguments to
super().__init__(). It needs the same arguments the parent’s__init__expects. Look at the parent and match them. - Reversing the relationship. The parent is the general thing. The child is the specific thing.
class Animal(Dog)is backwards. A dog is an animal, not the other way around. - Using inheritance when “is a” does not fit. A
Caris not anEngine. It has an engine. When the link is “has a”, store the other object as data instead. Do not inherit.
Here is the argument mistake side by side:
class Animal: def __init__(self, name): self.name = name
class Dog(Animal): def __init__(self, name, breed): # ❌ Avoid: parent needs a name, none given super().__init__() # ✅ Good: pass the name the parent expects super().__init__(name) self.breed = breed✅ Best Practices
Keep these habits and inheritance stays clean:
- Use inheritance only for a real “is a” link. Say the sentence out loud first.
- Put the shared code in the parent, and only the differences in each child.
- Call
super().__init__(...)as the first line of the child’s__init__. - When you override a method, keep its job the same. A
speakshould still make the animal speak, just differently. - Do not build deep towers of inheritance. One parent and a few children is easy to read. Five levels deep is hard to follow.
🧩 What You’ve Learned
A quick recap of the ideas you can now use:
- ✅ Inheritance lets a child class reuse all the variables and methods of a parent class, so you write shared code once.
- ✅ You create it with
class Child(Parent):, and it models an “is a” relationship. - ✅ The child gets the parent’s methods for free and can add its own on top.
- ✅
super().__init__(...)runs the parent’s constructor so the parent’s setup still happens. - ✅ A child can override a parent method by defining one with the same name, and the child’s version wins.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
What does class Dog(Animal): mean?
Why: The class in parentheses is the parent, so Dog is the child and inherits from Animal.
- 2
Why call super().__init__(name) inside a child's __init__?
Why: super().__init__() runs the parent constructor, so the data the parent sets up is not lost.
- 3
Which relationship is a good fit for inheritance?
Why: Inheritance models 'is a'; a SavingsAccount is a BankAccount, while the others are 'has a' links.
- 4
What happens when a child defines a method with the same name as the parent's?
Why: This is overriding: the child's method replaces the parent's for objects of the child class.
🚀 What’s Next?
You can now share code between classes with inheritance. Next you’ll see how different classes can respond to the same method call in their own way. That makes your code flexible and clean.