Python 面向对象编程:类、继承、多态与组合

目录

  • [1. 引言](#1. 引言 "#sec-1")
  • [2. 类与实例](#2. 类与实例 "#sec-2")
    • [2.1 定义一个类](#2.1 定义一个类 "#sec-2-1")
    • [2.2 创建实例](#2.2 创建实例 "#sec-2-2")
    • [2.3 self 是什么](#2.3 self 是什么 "#sec-2-3")
  • [3. 属性与方法](#3. 属性与方法 "#sec-3")
    • [3.1 实例属性与类属性](#3.1 实例属性与类属性 "#sec-3-1")
    • [3.2 实例方法、类方法与静态方法](#3.2 实例方法、类方法与静态方法 "#sec-3-2")
    • [3.3 属性封装与 property](#3.3 属性封装与 property "#sec-3-3")
  • [4. 继承与方法重写](#4. 继承与方法重写 "#sec-4")
    • [4.1 基本继承](#4.1 基本继承 "#sec-4-1")
    • [4.2 方法重写与 super()](#4.2 方法重写与 super() "#sec-4-2")
    • [4.3 多重继承与 MRO](#4.3 多重继承与 MRO "#sec-4-3")
  • [5. 多态](#5. 多态 "#sec-5")
  • [6. 组合优于继承的实践](#6. 组合优于继承的实践 "#sec-6")
    • [6.1 继承的问题](#6.1 继承的问题 "#sec-6-1")
    • [6.2 用组合表达"has-a"](#6.2 用组合表达"has-a" "#sec-6-2")
    • [6.3 一个更贴近实战的例子](#6.3 一个更贴近实战的例子 "#sec-6-3")
    • [6.4 选择建议](#6.4 选择建议 "#sec-6-4")
  • [7. 总结](#7. 总结 "#sec-7")

1. 引言

面向对象编程(OOP)是 Python 中最常用的编程范式之一。它把数据和对数据的操作封装在一起,让代码更贴近现实世界的建模方式,也更容易复用和扩展。

本文围绕五个核心主题展开:类与实例、属性与方法、继承与方法重写、多态,以及组合优于继承的实践。读完后你应当能够独立设计出结构清晰、易于维护的类体系。

下面这张图展示了本文的知识脉络,方便你建立整体认知:

flowchart TD A["Python 面向对象编程"] --> B["类与实例"] A --> C["属性与方法"] A --> D["继承与方法重写"] A --> E["多态"] A --> F["组合优于继承"] B --> B1["class 定义 / 实例化 / self"] C --> C1["实例属性 / 类属性 / property"] D --> D1["重写 / super() / MRO"] E --> E1["鸭子类型 / 抽象基类"] F --> F1["has-a 委托 / 依赖注入"]

2. 类与实例

2.1 定义一个类

在 Python 中,使用 class 关键字定义类,类名通常采用大驼峰命名法。

python 复制代码
class Dog:
    """一个简单的狗类"""

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def bark(self):
        return f"{self.name} 在汪汪叫"

2.2 创建实例

类只是模板,实例才是真正占用内存、携带具体数据的对象。

python 复制代码
d1 = Dog("旺财", 3)
d2 = Dog("小黑", 5)

print(d1.bark())  # 旺财 在汪汪叫
print(d2.bark())  # 小黑 在汪汪叫
print(d1 is d2)   # False,两个独立实例

每个实例都有自己独立的属性空间,d1.name 和 d2.name 互不影响。

2.3 self 是什么

self 代表当前实例本身。调用 d1.bark() 时,Python 实际上执行的是 Dog.bark(d1),self 就是 d1。它只是约定俗成的名字,但强烈建议不要改成别的。

3. 属性与方法

3.1 实例属性与类属性

  • 实例属性 :在 __init__ 中通过 self.xxx 定义,每个实例各有一份。
  • 类属性:直接定义在类体中,所有实例共享。
python 复制代码
class Dog:
    species = "犬科"          # 类属性,所有实例共享

    def __init__(self, name):
        self.name = name      # 实例属性,每个实例独立

d1 = Dog("旺财")
d2 = Dog("小黑")

print(d1.species, d2.species)  # 犬科 犬科
Dog.species = "Canidae"
print(d1.species)              # Canidae,类属性被修改后所有实例可见

注意:通过实例给同名属性赋值,会在实例上新建一个属性,从而"遮蔽"类属性。

python 复制代码
d1.species = "自定义"
print(d1.species)  # 自定义
print(d2.species)  # Canidae

3.2 实例方法、类方法与静态方法

python 复制代码
class Circle:
    pi = 3.14159

    def __init__(self, radius):
        self.radius = radius

    def area(self):                 # 实例方法:第一个参数是 self
        return self.pi * self.radius ** 2

    @classmethod
    def from_diameter(cls, diameter):  # 类方法:第一个参数是 cls
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):         # 静态方法:没有 self/cls
        return r > 0
  • 实例方法用于操作具体对象的数据;
  • 类方法常用于提供替代构造器;
  • 静态方法只是放在类命名空间里的普通函数。

3.3 属性封装与 property

Python 没有真正的私有成员,约定用下划线表示非公开:_name 表示受保护,__name 会触发名称改写(name mangling)。

需要校验或计算属性时,用 @property 更优雅:

python 复制代码
class Person:
    def __init__(self, age):
        self._age = age

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("年龄不能为负数")
        self._age = value

p = Person(20)
p.age = 25
print(p.age)   # 25
# p.age = -1   # 抛出 ValueError

4. 继承与方法重写

4.1 基本继承

子类在类名后的括号中写明父类,即可继承父类的属性和方法。

python 复制代码
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return "..."

class Cat(Animal):
    def speak(self):
        return f"{self.name}:喵喵"

class Dog(Animal):
    def speak(self):
        return f"{self.name}:汪汪"

上面的继承关系可以用下图表示:

classDiagram class Animal { +name +speak() } class Cat { +speak() } class Dog { +speak() } class Puppy { +age +speak() } Animal <|-- Cat Animal <|-- Dog Dog <|-- Puppy

4.2 方法重写与 super()

子类定义与父类同名的方法即为重写。若仍想复用父类逻辑,用 super() 调用。

python 复制代码
class Puppy(Dog):
    def __init__(self, name, age):
        super().__init__(name)   # 复用父类初始化
        self.age = age

    def speak(self):
        base = super().speak()   # 复用父类行为
        return f"{base}(奶声奶气)"

p = Puppy("豆豆", 1)
print(p.speak())  # 豆豆:汪汪(奶声奶气)

4.3 多重继承与 MRO

Python 支持多重继承,方法查找顺序由 MRO(方法解析顺序)决定,遵循 C3 线性化算法。

python 复制代码
class A:
    def hello(self):
        return "A"

class B(A):
    def hello(self):
        return "B -> " + super().hello()

class C(A):
    def hello(self):
        return "C -> " + super().hello()

class D(B, C):
    pass

print(D().hello())      # B -> C -> A
print([c.__name__ for c in D.__mro__])  # ['D', 'B', 'C', 'A', 'object']

D 的方法查找顺序(MRO)如下图所示,super() 会沿着这条链依次向上调用:

flowchart LR D[&#34;D&#34;] --> B[&#34;B&#34;] B --> C[&#34;C&#34;] C --> A[&#34;A&#34;] A --> O[&#34;object&#34;]

多重继承虽然强大,但容易带来复杂度,实际项目中应谨慎使用。

5. 多态

多态指同一接口在不同类型上有不同实现。Python 是动态类型语言,天然支持"鸭子类型":只要对象有需要的方法,就能被统一处理,不要求继承关系。

python 复制代码
class Duck:
    def speak(self):
        return "嘎嘎"

class Robot:
    def speak(self):
        return "滴滴"

def make_sound(obj):
    print(obj.speak())

for item in [Duck(), Robot(), Cat("咪咪")]:
    make_sound(item)

输出:

text 复制代码
嘎嘎
滴滴
咪咪:喵喵

make_sound 不关心对象的具体类型,只关心它是否有 speak 方法。这正是多态带来的解耦能力。

多态的调用过程可以概括为下图:

flowchart TD S[&#34;make_sound(obj)&#34;] --> Q{&#34;obj 是否有 speak 方法&#34;} Q -->|&#34;是&#34;| R[&#34;调用 obj.speak()&#34;] Q -->|&#34;否&#34;| X[&#34;抛出 AttributeError&#34;] R --> D1[&#34;Duck:嘎嘎&#34;] R --> D2[&#34;Robot:滴滴&#34;] R --> D3[&#34;Cat:喵喵&#34;]

如果需要显式约束接口,可以使用抽象基类:

python 复制代码
from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        ...

class Square(Shape):
    def __init__(self, side):
        self.side = side

    def area(self):
        return self.side ** 2

# Shape()  # TypeError:抽象类不能实例化
print(Square(3).area())  # 9

6. 组合优于继承的实践

6.1 继承的问题

继承表达的是"is-a"关系。当子类只是为了复用代码而继承时,往往会出现:

  • 继承层次过深,理解成本高;
  • 父类改动波及所有子类(脆弱基类问题);
  • 多重继承导致 MRO 复杂、行为难以预测。

6.2 用组合表达"has-a"

组合是把一个对象作为另一个对象的属性,通过委托完成功能复用,表达"has-a"关系。

python 复制代码
class Engine:
    def start(self):
        return "引擎启动"

class Wheel:
    def roll(self):
        return "车轮滚动"

class Car:
    def __init__(self):
        self.engine = Engine()
        self.wheels = [Wheel() for _ in range(4)]

    def start(self):
        return self.engine.start()

    def run(self):
        return ",".join(w.roll() for w in self.wheels)

car = Car()
print(car.start())  # 引擎启动
print(car.run())    # 车轮滚动,车轮滚动,车轮滚动,车轮滚动

Car 不需要继承 Engine,只需持有一个引擎对象。替换引擎实现时,Car 的代码几乎不用改。

组合关系如下图所示,Car 通过持有 Engine 与 Wheel 完成功能复用:

classDiagram class Car { +Engine engine +Wheel[] wheels +start() +run() } class Engine { +start() } class Wheel { +roll() } Car *-- Engine Car *-- Wheel

6.3 一个更贴近实战的例子

假设要记录日志,可以按输出目标拆分策略,而不是为每种组合写一个子类。

python 复制代码
class ConsoleWriter:
    def write(self, msg):
        print(f"[console] {msg}")

class FileWriter:
    def __init__(self, path):
        self.path = path

    def write(self, msg):
        with open(self.path, "a", encoding="utf-8") as f:
            f.write(msg + "\n")

class Logger:
    def __init__(self, writer):
        self.writer = writer

    def log(self, msg):
        self.writer.write(msg)

Logger(ConsoleWriter()).log("hello")
Logger(FileWriter("app.log")).log("hello")

如果改用继承,就需要 ConsoleLogger、FileLogger,再叠加格式化、过滤等维度时子类数量会爆炸。组合让每个维度独立变化。

6.4 选择建议

场景 推荐
子类确实是父类的一种(is-a) 继承
只是想复用某个类的功能(has-a) 组合
需要运行时切换行为 组合 + 依赖注入
需要定义统一接口 抽象基类 / 协议

一句话总结:优先使用组合,只有在明确的 is-a 关系且父类设计稳定时才使用继承。

7. 总结

  • 类定义模板,实例承载具体数据,self 指向当前实例;
  • 实例属性各自独立,类属性共享,property 让属性访问更可控;
  • 继承用于表达 is-a 关系,重写配合 super() 复用父类逻辑;
  • 多态让调用方只依赖接口,Python 的鸭子类型让这一点更自然;
  • 组合通过委托复用功能,通常比继承更灵活、更易维护。

在实际项目中,先想清楚对象之间是"是什么"还是"有什么",往往就能做出正确的结构选择。

相关推荐
高洁0118 分钟前
知识图谱:结构化表达实体与关系的知识组织技术
人工智能·python·深度学习·机器学习·迁移学习
骊山道童24 分钟前
第 1 周 LLM 原理与高效微调知识精讲手册
python
Metaphor6921 小时前
轻松使用 Python 将 RTF 转换为 Word(含批量转换与格式互转)
python·word·rtf
zh路西法1 小时前
【深入理解 LSTM】从时间反向传播(BPTT)到长时记忆:门控机制与实验验证
pytorch·python·rnn·gru·lstm·bptt
青少儿编程课堂1 小时前
蓝桥杯青少组 + 全国青少年信息素养大赛 + 电子学会图形化考级|校园推箱子大挑战解析
c++·python·算法·bfs·信息学竞赛
2601_966949651 小时前
Python 实战:如何统计五档盘口买卖盘总挂单量?附计算公式与代码
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
全栈陈序员2 小时前
【Python】基础语法入门(二十九)—— 正则表达式实战:文本处理利器
开发语言·人工智能·python·正则表达式
得物技术2 小时前
Jev 实战:为AI工作流引入轻量决策层 x10倍提速|得物技术
python·llm·测试
reasonsummer2 小时前
【教学类-07-12】20261028大3班破译电话号码html(手机按钮版本)
python·html
摇滚侠2 小时前
《On Java 中文版 基础卷》阅读笔记 复用 13
java·笔记·python