What is Inheritance?
Inheritance allows you to create a new class (child class) based on an existing class (parent class). The child class inherits all attributes and methods from the parent class, and can add new ones or override existing ones.
Basic Inheritance
Create a child class that inherits from a parent:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound"
class Dog(Animal): # Dog inherits from Animal
def speak(self): # Override parent method
return f"{self.name} barks"
class Cat(Animal): # Cat inherits from Animal
def speak(self): # Override parent method
return f"{self.name} meows"
# Use the classes
dog = Dog("Buddy")
cat = Cat("Whiskers")
print(dog.speak()) # "Buddy barks"
print(cat.speak()) # "Whiskers meows"
Inheriting Attributes
Child classes inherit parent attributes:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def introduce(self):
return f"I'm {self.name}, {self.age} years old"
class Student(Person):
def __init__(self, name, age, student_id):
super().__init__(name, age) # Call parent __init__
self.student_id = student_id
def study(self):
return f"{self.name} is studying"
student = Student("Alice", 20, "S123")
print(student.introduce()) # Uses inherited method
print(student.study()) # Uses new method
The super() Function
Use super() to call parent class methods:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side) # Call parent __init__
# Square is a rectangle with equal sides
square = Square(5)
print(square.area()) # 25 (uses inherited method)
Method Overriding
Override parent methods in child class:
class Vehicle:
def start(self):
return "Vehicle starting..."
class Car(Vehicle):
def start(self): # Override parent method
return "Car engine starting..."
class ElectricCar(Car):
def start(self): # Override again
return "Electric car silently starting..."
car = Car()
ecar = ElectricCar()
print(car.start()) # "Car engine starting..."
print(ecar.start()) # "Electric car silently starting..."
Multiple Inheritance
Python supports inheriting from multiple parents:
class Flyable:
def fly(self):
return "Flying..."
class Swimmable:
def swim(self):
return "Swimming..."
class Duck(Flyable, Swimmable): # Inherits from both
def quack(self):
return "Quack!"
duck = Duck()
print(duck.fly()) # "Flying..."
print(duck.swim()) # "Swimming..."
print(duck.quack()) # "Quack!"
Checking Inheritance
Check relationships between classes:
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
# Check if instance is of class
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
# Check if class is subclass
print(issubclass(Dog, Animal)) # True
print(issubclass(Animal, Dog)) # False
Protected and Private Attributes
Use naming conventions for inheritance:
class Parent:
def __init__(self):
self.public = "public" # Accessible everywhere
self._protected = "protected" # Convention: should not be accessed outside
self.__private = "private" # Name mangling: harder to access
class Child(Parent):
def show(self):
print(self.public) # OK
print(self._protected) # OK (but convention says don't)
# print(self.__private) # Error! Name mangled
Abstract Base Classes
Create classes that must be inherited (using ABC):
from abc import ABC, abstractmethod
class Shape(ABC): # Abstract base class
@abstractmethod
def area(self):
pass # Must be implemented by child classes
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self): # Must implement
return self.width * self.height
# shape = Shape() # Error! Cannot instantiate abstract class
rect = Rectangle(5, 3)
print(rect.area()) # 15
Common Patterns
Template Method Pattern
class DataProcessor:
def process(self):
data = self.load()
processed = self.transform(data)
self.save(processed)
def load(self):
raise NotImplementedError("Subclass must implement")
def transform(self, data):
return data # Default implementation
def save(self, data):
raise NotImplementedError("Subclass must implement")
class CSVProcessor(DataProcessor):
def load(self):
return "CSV data"
def save(self, data):
print(f"Saving {data} to CSV")
Best Practices
- Use inheritance for "is-a" relationships: Dog is an Animal
- Use composition for "has-a" relationships: Car has an Engine
- Don't override unnecessarily: Only override when you need different behavior
- Use super() consistently: Call parent methods when appropriate
- Avoid deep inheritance: Keep inheritance hierarchies shallow
Inheritance vs Composition
Sometimes composition (has-a) is better than inheritance (is-a):
# Inheritance (is-a)
class Car(Vehicle):
pass
# Composition (has-a) - often better
class Car:
def __init__(self):
self.engine = Engine() # Car has an engine
self.wheels = [Wheel() for _ in range(4)]
Next Steps
Review classes and objects to understand the foundation, or explore dunder methods for advanced class customization.