Python oop: A practical guide to inheritance and polymorphism

1. Inheritance basics

One of the charms of oop (OOP) lies in the core feature of inheritance: it allows us to **develop on the shoulders of giants" - quickly create "subclasses (derived classes)" based on the existing "parent class (base class/superclass)", automatically reuse all attributes and methods of the parent class, and flexibly extend or modify specific functions.

1.1 Minimalist inheritance syntax

Python's inheritance syntax is very intuitive. Just fill in the name of the parent class you want to inherit in the parentheses of the subclass definition:

class ParentClass:
    # 父类的属性、方法定义
    pass

class ChildClass(ParentClass):
    # 子类的扩展/修改定义
    pass

1.2 Concrete inheritance scenario

Let’s take the classic “animal classification” scenario as an example: first define the attributes and common behaviors shared by all animals, and then subdivide the exclusive behaviors of different animals.

# 父类:定义所有动物的通用属性(名字)和通用行为(发出声音)
class Animal:
    def __init__(self, name):
        self.name = name  # 所有动物都有的属性

    def speak(self):
        print(f"{self.name} makes a generic sound")  # 通用叫声

# 子类1:狗,继承 Animal
class Dog(Animal):
    # 重写(覆盖)通用的 speak 方法,改成狗的专属行为
    def speak(self):
        print(f"{self.name} barks: Woof Woof!")

# 子类2:猫,同样继承 Animal
class Cat(Animal):
    # 重写通用 speak 方法
    def speak(self):
        print(f"{self.name} meows: Meow Meow~")

2. Direct benefits brought by inheritance

2.1 Code reuse and reduce redundancy

If there is no inheritance, do we have to giveDogCatBirdWrite them all separately__init__Set name? But with inheritance, this part of the code is directly "borrowed" from the subclass. We only need to pay attention to the different places:

# 调用父类的通用方法(可选重写的情况)
generic_animal = Animal("无名小兽")
generic_animal.speak()  # 输出:无名小兽 makes a generic sound

# 调用子类自己重写的方法
buddy = Dog("Buddy")
buddy.speak()  # 输出:Buddy barks: Woof Woof!

whiskers = Cat("Whiskers")
whiskers.speak()  # 输出:Whiskers meows: Meow Meow~

2.2 Flexible expansion functions

In addition to overriding methods, subclasses can also add exclusive attributes or methods without affecting the parent class and other sibling classes at all:

# 新增鸟类,继承 Animal
class Bird(Animal):
    # 1. 重写通用 speak
    def speak(self):
        print(f"{self.name} chirps: Tweet Tweet♪")

    # 2. 新增专属方法:飞行
    def fly(self, height="low"):
        print(f"{self.name} is flying {height} in the sky~")

# 测试专属方法
tweety = Bird("Tweety")
tweety.speak()  # 通用部分的重写生效
tweety.fly("high")  # 专属方法只能由 Bird 实例调用

3. Polymorphism: same interface, different responses

After understanding inheritance, it is simple to look at Polymorphism - it refers to objects of different classes. When calling methods with "identical names", they will execute their own exclusive implementation, just like giving the "open mouth and bark" instruction to all animals. Dogs will bark, cats will meow, and birds will chirp.

3.1 Classic polymorphism example

We can write a "general calling function" without having to consider the specific type of object passed in, as long as it hasspeakJust the method:

def make_it_speak(thing):
    # 不管 thing 是什么,只要能 speak 就执行
    thing.speak()

# 把不同类的实例放到一个列表里
mixed_things = [Dog("Rex"), Cat("Mittens"), Bird("Tweety")]

# 遍历列表,统一调用 make_it_speak
for thing in mixed_things:
    make_it_speak(thing)

Is the output of this code very intuitive?

Rex barks: Woof Woof!
Mittens meows: Meow Meow~
Tweety chirps: Tweet Tweet♪

3.2 The "opening and closing principle" behind polymorphism

Polymorphism perfectly fits one of the golden rules of OOP - Open/Closed Principle:

  • Open to Extensions: If you want to add a newFishClass, just need to inheritAnimalrewritespeak, no need to changemake_it_speakthis general function.
  • Closed to modification: Existing code that relies on the parent class (such as general functions, list traversal) does not need to be touched at all.

4. Simple type checking

Although Python is a dynamic language and has relatively loose restrictions on types, sometimes we need to know clearly "whether an object is an instance of a certain class" and "whether a certain class inherits from another class". In this case, we can use two built-in functions.

4.1 isinstance(): Check instance type

rex = Dog("Rex")

# 检查 rex 是不是 Dog 的实例(直接类型)
print(isinstance(rex, Dog))    # True
# 检查 rex 是不是 Animal 的实例(继承类型)
print(isinstance(rex, Animal)) # True
# 检查 rex 是不是 Cat 的实例(无关类型)
print(isinstance(rex, Cat))    # False

4.2 issubclass(): Check the inheritance relationship of the class

# 检查 Dog 是不是继承自 Animal
print(issubclass(Dog, Animal))      # True
# 检查 Animal 是不是继承自自己(Python 中所有类都是自己的子类)
print(issubclass(Animal, Animal))  # True
# 检查 Animal 是不是继承自 Dog(反向继承不存在)
print(issubclass(Animal, Dog))     # False

5. Python’s unique “duck typing”

Unlike static languages ​​such as Java and C++, Python does not mandate that "polymorphism must be based on inheritance relationships" - it pursues "Duck Typing":

If it walks like a duck and quacks like a duck, then it's a duck!

In other words, Python only cares about "whether the object has the required methods" and does not care about "which class it belongs to". To the polymorphic example above, we can add a completely unrelatedCarClass try:

# 完全不继承 Animal 的 Car 类,但有 speak 方法
class Car:
    def speak(self):
        print("A car makes a sound: Vroom Vroom!")

# 把 Car 实例也加到 mixed_things 里
mixed_things.append(Car())

# 再遍历一遍,通用函数依然能用!
for thing in mixed_things:
    make_it_speak(thing)

The new output will be:

A car makes a sound: Vroom Vroom!

6. Several practical best practices

Inheritance and polymorphism are powerful, but misuse can lead to code complexity. Here are 4 rules of thumb:

  1. Prefer to use "composition" rather than "inheritance": Inheritance is only used when the two classes have an "is-a" relationship (such as "a dog is an animal"); if there is a "has-a" relationship (such as "a person has a dog"), it is more flexible to use combination.
  2. Try to avoid "multiple inheritance": Although Python supports it, multiple inheritance will lead to the "diamond inheritance problem" (two parent classes inherit from the same ancestor, and subclasses will be confused when calling ancestor methods), unless there are clear design requirements.
  3. Use abstract base class (ABC) to define interface specifications: If you want all subclasses to implement certain methods, you can useabcModules define abstract base classes so that Python enforces constraints on subclasses.
  4. Follow the "Richter Substitution Principle": The subclass should be able to completely replace the position of the parent class without causing program errors (such as the parent class'sspeakOnly print information, subclassesspeakJust don't delete files suddenly).

6.1 A quick look at an example of an abstract base class

Abstract base classes cannot be instantiated directly and must be inherited by subclasses and implement all tags.@abstractmethodmethod:

from abc import ABC, abstractmethod

# 定义抽象基类 Shape,要求所有形状必须计算面积和周长
class Shape(ABC):
    @abstractmethod  # 强制子类必须实现的方法
    def area(self):
        pass

    @abstractmethod
    def perimeter(self):
        pass

# 子类 Rectangle,必须实现 area 和 perimeter
class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

# 实例化成功
rect = Rectangle(3, 4)
print(rect.area())      # 输出:12
print(rect.perimeter()) # 输出:14

# 尝试直接实例化抽象基类会报错
# shape = Shape()  # 这行代码会抛出 TypeError

7. Summary

Inheritance and polymorphism are the core frameworks of Python OOP:

  • Inheritance: Implement code reuse and hierarchical class structure, and develop on the shoulders of giants.
  • Polymorphism: The same interface corresponds to different implementations, improving the flexibility and scalability of the code.
  • Duck typing: Python's "loose constraints" feature makes code more concise and versatile.

Remember, tools are not good or bad, and proper use is the key - give priority to combination, avoid multiple inheritance, and use abstract base classes as constraints, and you can write clear and easy-to-maintain object-oriented code!