Object-Oriented Design in Python: How to Elegantly Use Inheritance and Avoid Pitfalls?

In actual development, inheritance is a double-edged sword. When used well, it enhances code reusability; when used poorly, it becomes a maintenance nightmare.

1. The Essence of Inheritance: When Should Inheritance Be Used?

Many developers have misconceptions about inheritance, believing it to be a universal key for code reuse. In fact, inheritance should serve two core objectives:

1. A True “Is-A” Relationship

When you say “Dog is an Animal”, this inheritance relationship is reasonable. However, if it becomes “Employee is a DatabaseConnection”, then it needs to be reconsidered.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


# ✅ Good Inheritance Example
class Animal:
    def speak(self):
        raise NotImplementedError
 
class Dog(Animal):
    def speak(self):
        return "Woof!"
 
# ❌ Bad Inheritance: A square is not a rectangle
class Rectangle:
    def set_size(self, width, height):
        self.width = width
        self.height = height
 
class Square(Rectangle):  # Violates Liskov Substitution Principle
    def set_size(self, width, height):
        if width != height:
            raise ValueError("Square width and height must be equal")
        super().set_size(width, height)


2. The Need for Polymorphism

When a unified interface is needed to handle different objects, inheritance is a good choice:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


class PaymentProcessor:
    def pay(self, amount):
        raise NotImplementedError
 
class AlipayProcessor(PaymentProcessor):
    def pay(self, amount):
        print(f"Alipay payment of {amount} yuan")
 
class WechatProcessor(PaymentProcessor):
    def pay(self, amount):
        print(f"WeChat payment of {amount} yuan")
 
# Unified payment processing
def process_payment(processor: PaymentProcessor, amount):
    processor.pay(amount)


2. Inheritance vs Composition: How to Choose?

Scenario Choose Inheritance Choose Composition
Clear “Is-A” Relationship
Need for Polymorphic Support
Function Composition
Runtime Dynamic Changes

Practical Application of Composition



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22


# Using Composition Instead of Inheritance
class Engine:
    def start(self):
        print("Engine started")
 
class MusicPlayer:
    def play(self):
        print("Playing music")
 
class Car:
    def __init__(self):
        self.engine = Engine()      # Composition: Car has an engine
        self.music_player = MusicPlayer()  # Composition: Car has a music player
    
    def drive(self):
        self.engine.start()
        print("Car is driving")
 
# Usage
my_car = Car()
my_car.drive()
my_car.music_player.play()


3. Essential Inheritance Design Principles

3.1 Liskov Substitution Principle: Subclasses Should Seamlessly Replace Superclasses

Core Idea: Subclasses should be able to completely replace superclasses without breaking the correctness of the program. This means that subclasses should not change the behavioral contracts of the superclass.

Violation Example Analysis:



1
2
3
4
5
6
7


class Bird:
    def fly(self):
        print("Flying")
 
class Ostrich(Bird):  # Ostriches cannot fly
    def fly(self):
        raise Exception("Ostriches cannot fly")


The problem here is: if the code expects to handle a Bird type but receives an Ostrich, it will throw an exception, breaking the expected behavior of the program.

Correct Approach:



1
2
3
4
5
6
7
8
9
10


class Bird:
    pass
 
class FlyingBird(Bird):
    def fly(self):
        print("Flying")
 
class Ostrich(Bird):  # A bird that cannot fly
    def run(self):
        print("Running fast")


3.2 Single Responsibility Principle: A Class Should Do One Thing

Core Idea: A class should have only one reason to change. If a class is found to have too many responsibilities, it should be split.

Violation Example Analysis:



1
2
3
4
5


class Employee:
    def calculate_salary(self): pass    # Calculate salary
    def save_to_database(self): pass    # Database operations
    def generate_report(self): pass     # Generate report
    def send_notification(self): pass   # Send notification


This Employee class takes on too many unrelated responsibilities, and any change in one area will affect the entire class.

Correct Approach:



1
2
3
4
5
6
7
8
9
10
11


class Employee:  # Core business logic
    def calculate_salary(self): pass
 
class EmployeeRepository:  # Data persistence
    def save(self, employee): pass
 
class ReportGenerator:  # Report generation
    def generate(self, employee): pass
 
class NotificationService:  # Notification service
    def send(self, employee, message): pass


3.3 Open/Closed Principle: Open for Extension, Closed for Modification

Core Idea: Software entities should be open for extension but closed for modification. This means that functionality should be extended by adding new code rather than modifying existing code.

Violation Example Analysis:



1
2
3
4
5
6
7
8
9


class PaymentProcessor:
    def process(self, payment_type, amount):
        if payment_type == "alipay":
            # Alipay processing logic
            pass
        elif payment_type == "wechat":
            # WeChat payment processing logic
            pass
        # Every time a new payment method is added, this class must be modified


Correct Approach:



1
2
3
4
5
6
7
8
9
10
11
12
13


class PaymentProcessor:
    def process(self, amount):
        raise NotImplementedError
 
class AlipayProcessor(PaymentProcessor):
    def process(self, amount):
        # Alipay specific logic
        pass
 
class WechatProcessor(PaymentProcessor):
    def process(self, amount):
        # WeChat payment specific logic
        pass


4. Practical Tips: Mixin Pattern

A Mixin is an elegant way to implement multiple inheritance in Python, providing specific functionality without instantiating separately.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20


class JsonSerializableMixin:
    """JSON serialization functionality"""
    def to_json(self):
        import json
        return json.dumps(self.__dict__)
 
class LoggableMixin:
    """Logging functionality"""
    def log(self, message):
        print(f"[{self.__class__.__name__}] {message}")
 
class User(JsonSerializableMixin, LoggableMixin):
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.log(f"User {name} has been created")
 
# Usage
user = User("Zhang San", "[email protected]")
print(user.to_json())  # Using Mixin provided functionality


5. Real Case Study: E-commerce System Design

Let’s look at how to apply these principles through the discount functionality of an e-commerce system:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48


from abc import ABC, abstractmethod
 
# Abstract base class defining the interface
class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, price: float) -> float:
        pass
 
# Concrete strategy implementations
class PercentageDiscount(DiscountStrategy):
    def __init__(self, percent):
        self.percent = percent
    
    def apply(self, price):
        return price * (1 - self.percent / 100)
 
class FixedAmountDiscount(DiscountStrategy):
    def __init__(self, amount):
        self.amount = amount
    
    def apply(self, price):
        return max(0, price - self.amount)
 
# Product class using composition
class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price
        self.discount_strategy = None
    
    def set_discount(self, strategy: DiscountStrategy):
        self.discount_strategy = strategy
    
    @property
    def final_price(self):
        if self.discount_strategy:
            return self.discount_strategy.apply(self.price)
        return self.price
 
# Usage example
iphone = Product("iPhone 15", 5999)
iphone.set_discount(PercentageDiscount(10))  # 10% off
 
book = Product("Python Programming", 99)
book.set_discount(FixedAmountDiscount(20))  # 20 yuan off
 
print(f"iPhone final price: {iphone.final_price}")
print(f"Book final price: {book.final_price}")


6. Pitfall Guide

1. Avoid Over-Inheritance



1
2
3
4
5
6
7
8
9
10
11
12
13
14


# ❌ Over-Inheritance: Too deep hierarchy
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Poodle(Dog): pass
class ToyPoodle(Poodle): pass  # Difficult to maintain!
 
# ✅ Use attributes instead of deep inheritance
class Dog:
    def __init__(self, breed, size="medium"):
        self.breed = breed
        self.size = size
 
toy_poodle = Dog("poodle", size="small")


2. Correctly Use super()



1
2
3
4
5
6
7
8
9
10


class Base:
    def __init__(self):
        print("Base class initialized")
        self.base_data = {}
 
class Child(Base):
    def __init__(self, name):
        super().__init__()  # Call parent class initialization first
        self.name = name
        print(f"Child class initialized: {name}")


7. Conclusion

Inheritance is an important feature of object-oriented programming, but it needs to be used with caution:

  1. 1. Prefer Composition, unless there is a clear “Is-A” relationship
  2. 2. Follow Design Principles, especially Liskov Substitution and Single Responsibility
  3. 3. Utilize Mixins to achieve functional composition
  4. 4. Keep Inheritance Hierarchies Flat, avoiding over-design
  5. 5. Use Abstract Base Classes to define clear interfaces

Remember: Inheritance represents an eternal relationship, not a temporary code reuse need. Before using inheritance, always ask yourself: Will this relationship change in the future?

Discussion Topic: What pitfalls related to inheritance have you encountered in actual projects? Feel free to share and discuss in the comments!

Related Reading:

– Python super() function: Unlocking the Hidden Power of Object-Oriented Programming

– Python Object-Oriented Inheritance – Method Overriding: From Basics to Advanced Practice

– Understanding Object-Oriented Terminology in Python: Classes, Derived Classes, Parent Classes, Subclasses, Classic Classes, and New-Style Classes

These are my personal insights; if you have other views, let’s discuss in the comments!!!

Follow me, to get more Python learning resources, practical projects, and industry trends! Reply “python learning” in the public account backend to get Python learning e-books!

Leave a Comment