难度等级: 中级-高级
适合读者: 有 Python 基础的开发者,准备面试的中高级工程师
前置知识: 第 01 篇《Python 数据结构全解析》、第 02 篇《函数式编程与 Python 魔法》
导读
面向对象编程(OOP)是构建复杂系统的基石。但在 Python 的世界里,OOP 有着鲜明的自身特色 -- 它不像 Java 那样强制一切皆对象,也不像 C++ 那样提供 public/protected/private 关键字。Python 的 OOP 哲学是:约定优于强制,鸭子类型优于显式接口,简单优于复杂。
如果你用过 Django 的 Model 继承体系,就见识过 __new__ 和元类的威力;如果你写过 FastAPI 的依赖注入,就实践过依赖倒置原则;如果你在代码审查中遇到过继承层次深达 5 层以上的"继承地狱",就能理解为什么"组合优于继承"是如此重要。
但在面试和实际开发中,很多人会在以下问题上卡壳:
__new__和__init__到底谁先执行?什么时候需要重写__new__?- Python 的 MRO 是什么?多重继承时
super()到底调用的是哪个父类的方法? - 鸭子类型和 ABC 抽象基类的适用场景分别是什么?
- SOLID 原则在 Python 这种动态语言中如何落地?
- 什么时候用继承,什么时候用组合?Mixin 又是什么?
本文将从 CPython 实现机制到 SOLID 设计原则,再到魔法方法的完整协议体系,系统性地拆解 Python OOP 的高级特性,帮助你在面试和项目中都能游刃有余。
学习目标
读完本文后,你将能够:
- 解释
__new__与__init__的分工,理解类的两阶段构造过程,能用__new__实现单例模式和不可变类型 - 掌握 MRO 与 C3 线性化算法,准确分析多重继承场景下的方法查找顺序
- 理解鸭子类型的核心哲学,区分 ABC 抽象基类与 Protocol 的适用场景
- 在 Python 项目中践行 SOLID 五大原则,写出高内聚、低耦合的代码
- 识别继承滥用的反模式,掌握组合模式和 Mixin 模式的正确使用方式
- 熟练实现常用魔法方法,让自定义类完美融入 Python 生态(可比较、可迭代、可哈希、支持上下文管理)
- 在面试中自信回答 OOP 相关的高频问题
一、Python OOP 核心机制
1.1 类的两阶段构造:__new__ vs __init__
在 Python 中,创建一个对象分为两步:分配内存(__new__) 和 初始化属性(__init__) 。绝大多数情况下我们只需要写 __init__,但理解 __new__ 是掌握高级 OOP 的关键。
python
class MyClass:
def __new__(cls, *args, **kwargs):
print(f"1. __new__ 被调用, cls={cls.__name__}")
instance = super().__new__(cls) # 创建实例(分配内存)
print(f" 实例 id: {id(instance)}")
return instance
def __init__(self, name: str):
print(f"2. __init__ 被调用, self id: {id(self)}")
self.name = name
obj = MyClass("Alice")
# 输出:
# 1. __new__ 被调用, cls=MyClass
# 实例 id: 140234567890
# 2. __init__ 被调用, self id: 140234567890
关键区别:
| 特征 | __new__ |
__init__ |
|---|---|---|
| 方法类型 | 静态方法(隐式) | 实例方法 |
| 第一个参数 | cls(类本身) |
self(实例) |
| 职责 | 创建并返回实例 | 初始化实例属性 |
| 返回值 | 必须返回实例 | 无返回值(返回 None) |
| 调用时机 | 先于 __init__ |
__new__ 返回实例后 |
底层原理 :当你调用 MyClass("Alice") 时,CPython 内部执行的逻辑大致是:
python
# 伪代码:CPython type.__call__ 的简化逻辑
def __call__(cls, *args, **kwargs):
instance = cls.__new__(cls, *args, **kwargs)
if isinstance(instance, cls):
instance.__init__(*args, **kwargs) # 仅当 __new__ 返回的是 cls 的实例时
return instance
注意:如果 __new__ 返回的不是当前类的实例,__init__ 就不会被调用。这个特性在实现某些设计模式时很有用。
实战一:用 __new__ 实现单例模式
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, value: str = "default"):
self.value = value
s1 = Singleton("first")
s2 = Singleton("second")
print(s1 is s2) # True -- 同一个实例
print(s1.value) # second -- 注意:__init__ 被调用了两次
print(id(s1) == id(s2)) # True
注意 :
__new__实现的单例虽然保证了只有一个实例,但__init__每次调用Singleton()都会执行,可能重复初始化。生产环境建议加一个标志位或使用元类方式。
实战二:用 __new__ 扩展不可变类型
继承不可变类型(如 int、str、tuple)时,必须在 __new__ 中设置值,因为 __init__ 时对象已经创建完毕,不可变类型无法再修改:
python
class PositiveInt(int):
"""只允许正整数的 int 子类"""
def __new__(cls, value: int):
if value <= 0:
raise ValueError(f"PositiveInt 必须为正数, 收到: {value}")
return super().__new__(cls, value)
n = PositiveInt(42)
print(n) # 42
print(type(n)) # <class '__main__.PositiveInt'>
print(n + 8) # 50
print(isinstance(n, int)) # True
try:
PositiveInt(-1)
except ValueError as e:
print(e) # PositiveInt 必须为正数, 收到: -1
1.2 MRO(方法解析顺序)与 C3 线性化
Python 支持多重继承,当多个父类有同名方法时,Python 使用 C3 线性化算法 确定方法的查找顺序,这个顺序称为 MRO(Method Resolution Order)。
钻石继承问题:
python
class A:
def method(self):
print("A.method")
class B(A):
def method(self):
print("B.method")
class C(A):
def method(self):
print("C.method")
class D(B, C):
pass
d = D()
d.method() # B.method -- 按 MRO 顺序查找
# 查看 MRO
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)
# 也可以用 mro() 方法
print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]
C3 线性化算法规则(简化理解):
-
子类永远在父类之前
-
多个父类的相对顺序保持与类定义中的声明顺序一致
-
以上规则递归应用于所有继承层级
D(B, C) 的 MRO 计算过程:
L[D] = D + merge(L[B], L[C], [B, C])
L[B] = B, A, object
L[C] = C, A, objectmerge([B, A, object], [C, A, object], [B, C])
→ 取 B(B 不在任何其他列表的尾部)
→ merge([A, object], [C, A, object], [C])
→ 取 C(A 在 [C,A,object] 尾部中出现,不能取;C 可以取)
→ merge([A, object], [A, object])
→ 取 A → 取 object最终:D → B → C → A → object
无效的 MRO -- Python 会报错:
python
# 某些继承结构无法线性化,Python 会拒绝
class X(A):
pass
class Y(A):
pass
# 如果 X 和 Y 的继承顺序与某个子类声明顺序矛盾,会抛出 TypeError
# 例如:class Z(X, Y, C, B) 如果 B 和 C 的 MRO 冲突
1.3 super() 的正确使用
super() 不是简单地"调用父类方法" -- 它按照 MRO 链调用下一个类的方法。这在多重继承时尤为重要:
python
class A:
def __init__(self):
print("A.__init__")
super().__init__()
class B(A):
def __init__(self):
print("B.__init__")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__")
super().__init__()
class D(B, C):
def __init__(self):
print("D.__init__")
super().__init__()
d = D()
# D.__init__
# B.__init__
# C.__init__ ← super() 在 B 中调用的不是 A,而是 MRO 链的下一个:C
# A.__init__
这就是 协作式多重继承 (cooperative multiple inheritance)的核心:每个类通过 super() 把调用传递给 MRO 链中的下一个,而不是硬编码调用某个特定的父类。
super() 的常见陷阱:
python
# 错误:混合使用 super() 和直接调用父类
class Base:
def __init__(self):
print("Base.__init__")
class Child(Base):
def __init__(self):
Base.__init__(self) # 直接调用,不走 MRO
# 在多重继承中,这会导致某些类的 __init__ 被跳过或重复调用
# 正确做法:统一使用 super()
class Child(Base):
def __init__(self):
super().__init__() # 遵循 MRO 链
带参数的 super() 调用(多重继承中参数传递的推荐模式):
python
class Base:
def __init__(self, **kwargs):
# 消费完自己不需要的参数后,剩余的传给下一个
print(f"Base.__init__ 剩余参数: {kwargs}")
super().__init__(**kwargs)
class LogMixin:
def __init__(self, log_level: str = "INFO", **kwargs):
self.log_level = log_level
print(f"LogMixin: log_level={log_level}")
super().__init__(**kwargs)
class CacheMixin:
def __init__(self, cache_ttl: int = 300, **kwargs):
self.cache_ttl = cache_ttl
print(f"CacheMixin: cache_ttl={cache_ttl}")
super().__init__(**kwargs)
class Service(LogMixin, CacheMixin, Base):
def __init__(self, name: str, **kwargs):
self.name = name
print(f"Service: name={name}")
super().__init__(**kwargs)
svc = Service(name="api", log_level="DEBUG", cache_ttl=600)
# Service: name=api
# LogMixin: log_level=DEBUG
# CacheMixin: cache_ttl=600
# Base.__init__ 剩余参数: {}
使用 **kwargs 传递参数是协作式多重继承的最佳实践,每个类从 kwargs 中取出自己需要的参数,其余的交给 MRO 链中的下一个类。
二、封装、继承、多态的 Python 实现
2.1 名称改编(Name Mangling)
Python 没有真正的 private 关键字,而是通过命名约定 和名称改编来实现访问控制:
python
class Account:
def __init__(self, owner: str, balance: float):
self.owner = owner # 公开属性
self._currency = "CNY" # 约定:受保护(外部可访问,但不建议)
self.__balance = balance # 名称改编:外部无法直接访问
def get_balance(self) -> float:
return self.__balance
def _internal_method(self):
"""单下划线:内部使用,但不阻止访问"""
return "internal"
def __private_method(self):
"""双下划线:名称改编"""
return "private"
acc = Account("Alice", 1000.0)
# 公开属性正常访问
print(acc.owner) # Alice
# 单下划线:约定上是内部使用,但可以访问
print(acc._currency) # CNY
# 双下划线:名称改编,直接访问会报错
try:
print(acc.__balance)
except AttributeError as e:
print(f"AttributeError: {e}")
# AttributeError: 'Account' object has no attribute '__balance'
# 名称改编的真相:Python 把 __balance 改名为 _Account__balance
print(acc._Account__balance) # 1000.0
# 查看实例的所有属性
print([attr for attr in dir(acc) if 'balance' in attr.lower()])
# ['_Account__balance', 'get_balance']
名称改编的设计意图 :名称改编不是为了"安全"(它很容易绕过),而是为了避免子类意外覆盖父类的内部属性:
python
class Base:
def __init__(self):
self.__data = "base_data" # 改编为 _Base__data
def get_data(self):
return self.__data
class Child(Base):
def __init__(self):
super().__init__()
self.__data = "child_data" # 改编为 _Child__data(不同的名字!)
c = Child()
print(c.get_data()) # base_data -- Base 的 __data 没有被覆盖
print(c._Child__data) # child_data
print(c._Base__data) # base_data
Python 访问控制的最佳实践:
| 命名方式 | 含义 | 真实行为 |
|---|---|---|
name |
公开 | 自由访问 |
_name |
受保护(约定) | 可以访问,但暗示"内部使用" |
__name |
私有(名称改编) | 改编为 _ClassName__name |
__name__ |
魔法方法 | Python 特殊用途,不要自创 |
2.2 鸭子类型(Duck Typing)
"如果它走起来像鸭子,叫起来像鸭子,那它就是鸭子。"
Python 的多态不依赖继承,而是依赖行为 -- 只要对象实现了所需的方法,就可以被使用:
python
class Duck:
def quack(self) -> str:
return "嘎嘎嘎"
def walk(self) -> str:
return "摇摇摆摆地走"
class Person:
def quack(self) -> str:
return "我在模仿鸭子叫"
def walk(self) -> str:
return "大步流星地走"
class RobotDuck:
def quack(self) -> str:
return "电子嘎嘎嘎"
def walk(self) -> str:
return "机械地移动"
def duck_test(duck) -> None:
"""不关心 duck 是什么类型,只要它能 quack 和 walk"""
print(f"叫声: {duck.quack()}")
print(f"走路: {duck.walk()}")
# 三个完全不相关的类,没有任何继承关系,但都能通过鸭子测试
for obj in [Duck(), Person(), RobotDuck()]:
duck_test(obj)
print("---")
鸭子类型 vs 显式接口:
python
from abc import ABC, abstractmethod
# 方式一:鸭子类型(Pythonic)
def process_payment_duck(processor):
"""任何有 charge 方法的对象都可以"""
return processor.charge(100.0)
# 方式二:ABC 显式接口(严格约束)
class PaymentProcessor(ABC):
@abstractmethod
def charge(self, amount: float) -> bool:
pass
class StripeProcessor(PaymentProcessor):
def charge(self, amount: float) -> bool:
print(f"Stripe 收费: {amount}")
return True
# ABC 会在实例化时强制检查
class BrokenProcessor(PaymentProcessor):
pass # 没有实现 charge
try:
BrokenProcessor()
except TypeError as e:
print(f"TypeError: {e}")
# TypeError: Can't instantiate abstract class BrokenProcessor
# with abstract method charge
选择建议:
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 内部模块间调用 | 鸭子类型 | 灵活、简洁 |
| 公开 API / 框架设计 | ABC 或 Protocol | 强约束、文档性强 |
需要 isinstance 检查 |
ABC | ABC 支持 register |
| 需要静态类型检查 | Protocol(Python 3.8+) | 兼顾鸭子类型和类型安全 |
2.3 Protocol -- 鸭子类型的静态检查(Python 3.8+)
typing.Protocol 实现了结构化子类型:不需要继承,只需要实现相同的方法签名就能通过类型检查:
python
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
"""任何有 draw 方法的对象都是 Drawable"""
def draw(self) -> str: ...
class Circle:
def draw(self) -> str:
return "画一个圆"
class Square:
def draw(self) -> str:
return "画一个方"
class NotDrawable:
pass
def render(shape: Drawable) -> None:
"""类型检查器(mypy)会验证传入的对象是否符合 Drawable"""
print(shape.draw())
# Circle 和 Square 没有继承 Drawable,但它们实现了 draw 方法
render(Circle()) # 画一个圆
render(Square()) # 画一个方
# runtime_checkable 允许运行时 isinstance 检查
print(isinstance(Circle(), Drawable)) # True
print(isinstance(NotDrawable(), Drawable)) # False
Protocol vs ABC:
| 特征 | Protocol | ABC |
|---|---|---|
| 是否需要继承 | 不需要 | 需要 |
| 检查方式 | 结构化(看方法签名) | 名义化(看继承关系) |
| 运行时检查 | 需要 @runtime_checkable |
天然支持 isinstance |
| 主要用途 | 类型标注、静态检查 | 定义接口约束 |
| Python 版本 | 3.8+ | 一直都有 |
三、SOLID 原则在 Python 中的实践
SOLID 是面向对象设计的五大原则。在 Python 这种动态语言中,SOLID 的某些原则需要用更 Pythonic 的方式来落地。
3.1 单一职责原则(SRP)
一个类应该只有一个引起它变化的原因。
python
# 违反 SRP:User 类同时负责业务逻辑和数据持久化
class UserBad:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def validate(self) -> bool:
return "@" in self.email and len(self.name) > 0
def save_to_db(self):
"""持久化逻辑不应该在实体类中"""
print(f"INSERT INTO users (name, email) VALUES ('{self.name}', '{self.email}')")
def send_welcome_email(self):
"""发送邮件逻辑也不应该在这里"""
print(f"Sending email to {self.email}")
# 遵循 SRP:每个类只有一个职责
class User:
"""实体类:只负责数据和业务规则"""
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def validate(self) -> bool:
return "@" in self.email and len(self.name) > 0
class UserRepository:
"""数据访问层:只负责持久化"""
def save(self, user: User) -> None:
if not user.validate():
raise ValueError("用户数据无效")
print(f"INSERT INTO users (name, email) VALUES ('{user.name}', '{user.email}')")
class EmailService:
"""通知服务:只负责发送邮件"""
def send_welcome(self, user: User) -> None:
print(f"Sending welcome email to {user.email}")
# 使用
user = User("Alice", "alice@example.com")
repo = UserRepository()
email_svc = EmailService()
repo.save(user)
email_svc.send_welcome(user)
3.2 开放封闭原则(OCP)
对扩展开放,对修改封闭 -- 新增功能时不需要修改已有代码。
python
from abc import ABC, abstractmethod
from typing import List
# 违反 OCP:每新增一种通知方式都要修改 Notifier 类
class NotifierBad:
def notify(self, message: str, method: str) -> None:
if method == "email":
print(f"[Email] {message}")
elif method == "sms":
print(f"[SMS] {message}")
elif method == "wechat":
print(f"[WeChat] {message}")
# 新增方式?继续加 elif ... 违反 OCP
# 遵循 OCP:通过抽象基类定义接口,新增通知方式只需新增类
class NotificationChannel(ABC):
@abstractmethod
def send(self, message: str) -> None:
pass
class EmailChannel(NotificationChannel):
def send(self, message: str) -> None:
print(f"[Email] {message}")
class SmsChannel(NotificationChannel):
def send(self, message: str) -> None:
print(f"[SMS] {message}")
class WeChatChannel(NotificationChannel):
def send(self, message: str) -> None:
print(f"[WeChat] {message}")
class Notifier:
"""通知器:对扩展开放,对修改封闭"""
def __init__(self, channels: List[NotificationChannel]):
self.channels = channels
def notify_all(self, message: str) -> None:
for channel in self.channels:
channel.send(message)
# 新增 Slack 通知只需要新建一个类,不用改 Notifier
class SlackChannel(NotificationChannel):
def send(self, message: str) -> None:
print(f"[Slack] {message}")
notifier = Notifier([EmailChannel(), SmsChannel(), SlackChannel()])
notifier.notify_all("服务器告警: CPU 使用率 95%")
# [Email] 服务器告警: CPU 使用率 95%
# [SMS] 服务器告警: CPU 使用率 95%
# [Slack] 服务器告警: CPU 使用率 95%
3.3 里氏替换原则(LSP)
子类必须能够替换父类,而不影响程序的正确性。
python
# 违反 LSP 的经典案例:正方形继承矩形
class Rectangle:
def __init__(self, width: float, height: float):
self._width = width
self._height = height
@property
def width(self) -> float:
return self._width
@width.setter
def width(self, value: float) -> None:
self._width = value
@property
def height(self) -> float:
return self._height
@height.setter
def height(self, value: float) -> None:
self._height = value
def area(self) -> float:
return self._width * self._height
class Square(Rectangle):
"""正方形'是'矩形?从数学角度看是,但从 LSP 角度看不是"""
def __init__(self, side: float):
super().__init__(side, side)
@Rectangle.width.setter
def width(self, value: float) -> None:
self._width = value
self._height = value # 正方形要求宽高相等
@Rectangle.height.setter
def height(self, value: float) -> None:
self._width = value
self._height = value
def resize_and_check(rect: Rectangle) -> None:
"""这个函数假设设置 width 不会影响 height"""
rect.width = 10
rect.height = 5
expected = 50 # 10 * 5
actual = rect.area()
print(f"期望面积: {expected}, 实际面积: {actual}, "
f"{'一致' if expected == actual else '不一致!'}")
resize_and_check(Rectangle(1, 1)) # 期望面积: 50, 实际面积: 50, 一致
resize_and_check(Square(1)) # 期望面积: 50, 实际面积: 25, 不一致!
遵循 LSP 的改写:使用共同的抽象基类,而不是让正方形继承矩形:
python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self) -> float:
pass
class RectangleGood(Shape):
def __init__(self, width: float, height: float):
self.width = width
self.height = height
def area(self) -> float:
return self.width * self.height
class SquareGood(Shape):
def __init__(self, side: float):
self.side = side
def area(self) -> float:
return self.side ** 2
# 两者都是 Shape,但互不继承
print(RectangleGood(10, 5).area()) # 50
print(SquareGood(5).area()) # 25
3.4 接口隔离原则(ISP)
客户端不应该被迫依赖它不使用的方法。
在 Python 中,用 Protocol 或小粒度的 ABC 来实践 ISP:
python
from typing import Protocol
# 违反 ISP:一个臃肿的接口
class WorkerBad(Protocol):
def work(self) -> None: ...
def eat(self) -> None: ...
def sleep(self) -> None: ...
# 问题:机器人也是 Worker,但它不需要 eat 和 sleep
# 遵循 ISP:拆分为细粒度的 Protocol
class Workable(Protocol):
def work(self) -> None: ...
class Eatable(Protocol):
def eat(self) -> None: ...
class Sleepable(Protocol):
def sleep(self) -> None: ...
class Human:
"""人类实现所有接口"""
def work(self) -> None:
print("人在工作")
def eat(self) -> None:
print("人在吃饭")
def sleep(self) -> None:
print("人在睡觉")
class Robot:
"""机器人只需要实现 Workable"""
def work(self) -> None:
print("机器人在工作")
def assign_work(worker: Workable) -> None:
"""只依赖 Workable 接口"""
worker.work()
def lunch_break(entity: Eatable) -> None:
"""只依赖 Eatable 接口"""
entity.eat()
assign_work(Human()) # 人在工作
assign_work(Robot()) # 机器人在工作
lunch_break(Human()) # 人在吃饭
# lunch_break(Robot()) # mypy 报错:Robot 没有 eat 方法
3.5 依赖倒置原则(DIP)
高层模块不应该依赖低层模块,两者都应该依赖抽象。
python
from abc import ABC, abstractmethod
from typing import List, Dict, Any
# 违反 DIP:Service 直接依赖具体的 MySQL 实现
class MySQLDatabaseBad:
def query(self, sql: str) -> List[Dict]:
return [{"id": 1, "name": "Alice"}]
class UserServiceBad:
def __init__(self):
self.db = MySQLDatabaseBad() # 直接依赖具体实现,无法替换
def get_users(self) -> List[Dict]:
return self.db.query("SELECT * FROM users")
# 遵循 DIP:依赖抽象接口,通过构造函数注入
class Database(ABC):
@abstractmethod
def query(self, sql: str) -> List[Dict[str, Any]]:
pass
class MySQLDatabase(Database):
def query(self, sql: str) -> List[Dict[str, Any]]:
print(f"[MySQL] 执行: {sql}")
return [{"id": 1, "name": "Alice"}]
class PostgreSQLDatabase(Database):
def query(self, sql: str) -> List[Dict[str, Any]]:
print(f"[PostgreSQL] 执行: {sql}")
return [{"id": 1, "name": "Alice"}]
class InMemoryDatabase(Database):
"""用于单元测试的内存数据库"""
def __init__(self):
self._data: List[Dict[str, Any]] = []
def query(self, sql: str) -> List[Dict[str, Any]]:
return self._data
class UserService:
def __init__(self, db: Database): # 依赖抽象,通过构造函数注入
self.db = db
def get_users(self) -> List[Dict[str, Any]]:
return self.db.query("SELECT * FROM users")
# 生产环境使用 MySQL
service = UserService(MySQLDatabase())
print(service.get_users())
# [MySQL] 执行: SELECT * FROM users
# [{'id': 1, 'name': 'Alice'}]
# 测试环境使用内存数据库
test_db = InMemoryDatabase()
test_db._data = [{"id": 99, "name": "TestUser"}]
test_service = UserService(test_db)
print(test_service.get_users())
# [{'id': 99, 'name': 'TestUser'}]
四、组合优于继承
4.1 继承滥用的反模式
继承应该表示 "is-a" 关系,但很多时候开发者为了代码复用而滥用继承,导致"继承地狱":
python
# 反模式:为了复用代码而滥用继承
class Engine:
def start(self) -> str:
return "引擎启动"
class CarBad(Engine):
"""Car 不是 Engine!Car 有 Engine"""
def drive(self) -> str:
return f"{self.start()} -> 开始行驶"
# 更糟糕的继承链
class ElectricEngineMixin:
def charge(self) -> str:
return "充电中"
class GPSMixin:
def navigate(self) -> str:
return "导航中"
class LoggerMixin:
def log(self, msg: str) -> str:
return f"[LOG] {msg}"
class SuperCarBad(CarBad, ElectricEngineMixin, GPSMixin, LoggerMixin):
"""5 层继承 + 多重继承 = 维护噩梦"""
pass
4.2 组合模式
使用组合替代继承 -- 表达 "has-a" 关系:
python
class Engine:
def start(self) -> str:
return "引擎启动"
class GPS:
def navigate(self, destination: str) -> str:
return f"导航到 {destination}"
class Logger:
def __init__(self, name: str):
self.name = name
def log(self, message: str) -> str:
return f"[{self.name}] {message}"
class Car:
"""使用组合:Car 拥有 Engine、GPS、Logger"""
def __init__(self, model: str):
self.model = model
self.engine = Engine() # has-a
self.gps = GPS() # has-a
self.logger = Logger(model) # has-a
def drive(self, destination: str) -> None:
print(self.logger.log(self.engine.start()))
print(self.logger.log(self.gps.navigate(destination)))
print(self.logger.log("行驶中..."))
car = Car("Tesla Model 3")
car.drive("公司")
# [Tesla Model 3] 引擎启动
# [Tesla Model 3] 导航到 公司
# [Tesla Model 3] 行驶中...
选择依据:
| 关系类型 | 用法 | 例子 |
|---|---|---|
| is-a(是) | 继承 | Dog is an Animal |
| has-a(有) | 组合 | Car has an Engine |
| behaves-like(行为像) | Protocol/Mixin | Logger behaves like Writable |
4.3 Mixin 模式
Mixin 是一种特殊的多重继承用法:提供可复用的功能片段,但不构成独立的抽象。Mixin 类的特点是:
- 不应该被单独实例化
- 不应该有
__init__(或者只有极简的__init__且使用**kwargs) - 只提供特定的方法或属性
python
import json
from typing import Dict, Any
class JsonSerializableMixin:
"""Mixin: 提供 JSON 序列化能力"""
def to_json(self) -> str:
return json.dumps(self._get_serializable_data(), ensure_ascii=False)
def _get_serializable_data(self) -> Dict[str, Any]:
"""默认序列化所有非下划线开头的属性"""
return {
k: v for k, v in self.__dict__.items()
if not k.startswith('_')
}
class TimestampMixin:
"""Mixin: 提供时间戳功能"""
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
# 自动为子类添加时间戳
original_init = cls.__init__ if hasattr(cls, '__init__') else None
def set_timestamps(self) -> None:
import time
now = time.time()
if not hasattr(self, 'created_at'):
self.created_at = now
self.updated_at = now
class ReprMixin:
"""Mixin: 自动生成 __repr__"""
def __repr__(self) -> str:
attrs = ", ".join(
f"{k}={v!r}"
for k, v in self.__dict__.items()
if not k.startswith('_')
)
return f"{self.__class__.__name__}({attrs})"
# 组合多个 Mixin
class User(JsonSerializableMixin, ReprMixin):
def __init__(self, name: str, email: str, age: int):
self.name = name
self.email = email
self.age = age
user = User("Alice", "alice@example.com", 30)
print(repr(user))
# User(name='Alice', email='alice@example.com', age=30)
print(user.to_json())
# {"name": "Alice", "email": "alice@example.com", "age": 30}
Mixin 的命名规范 :类名以 Mixin 结尾,如 JsonSerializableMixin、LoggingMixin、CacheMixin。这是 Python 社区的普遍约定。
4.4 继承 vs 组合 vs Mixin 性能对比
python
import timeit
# 方式1:深继承链
class Base1:
def method(self) -> int:
return 42
class Level1(Base1): pass
class Level2(Level1): pass
class Level3(Level2): pass
class Level4(Level3): pass
class DeepChild(Level4): pass
# 方式2:直接实现
class DirectClass:
def method(self) -> int:
return 42
# 方式3:组合
class Component:
def method(self) -> int:
return 42
class ComposedClass:
def __init__(self):
self._component = Component()
def method(self) -> int:
return self._component.method()
deep = DeepChild()
direct = DirectClass()
composed = ComposedClass()
# 性能对比
t_deep = timeit.timeit(lambda: deep.method(), number=1000000)
t_direct = timeit.timeit(lambda: direct.method(), number=1000000)
t_composed = timeit.timeit(lambda: composed.method(), number=1000000)
print(f"深继承链: {t_deep:.4f}s")
print(f"直接实现: {t_direct:.4f}s")
print(f"组合委托: {t_composed:.4f}s")
典型结果:
深继承链: 0.0850s
直接实现: 0.0750s
组合委托: 0.1100s
分析 :深继承链查找方法时需要遍历 MRO 链,但 CPython 有方法缓存机制,性能差距不大。组合方式因为多了一次属性访问和函数调用,略慢一些。性能差距在毫秒级别,设计可维护性远比这点性能差异重要。
五、魔法方法(Dunder Methods)大全
魔法方法(双下划线方法,Dunder Methods)是 Python 数据模型的核心。通过实现这些方法,自定义类可以完美融入 Python 的语法和内置函数体系。
5.1 对象表示:__repr__ 与 __str__
python
class Money:
def __init__(self, amount: float, currency: str = "CNY"):
self.amount = amount
self.currency = currency
def __repr__(self) -> str:
"""开发者看的:无歧义,可用于重建对象"""
return f"Money(amount={self.amount!r}, currency={self.currency!r})"
def __str__(self) -> str:
"""用户看的:可读性优先"""
symbols = {"CNY": "¥", "USD": "$", "EUR": "€"}
symbol = symbols.get(self.currency, self.currency)
return f"{symbol}{self.amount:,.2f}"
def __format__(self, format_spec: str) -> str:
"""支持 format() 和 f-string 的格式化"""
if format_spec == "short":
return f"{self.currency} {self.amount:.0f}"
return str(self)
m = Money(12345.6, "CNY")
print(repr(m)) # Money(amount=12345.6, currency='CNY')
print(str(m)) # ¥12,345.60
print(f"{m}") # ¥12,345.60
print(f"{m:short}") # CNY 12346
# repr 的黄金准则:理想情况下 eval(repr(obj)) == obj
m2 = eval(repr(m))
print(m2.amount) # 12345.6
__repr__ vs __str__ 的调用时机:
| 场景 | 调用方法 |
|---|---|
repr(obj) |
__repr__ |
str(obj) |
__str__(没有则回退到 __repr__) |
print(obj) |
__str__ |
f"{obj}" |
__str__ |
交互式解释器直接输入 obj |
__repr__ |
容器中显示(如 [obj]) |
__repr__ |
原则:每个类都应该实现 __repr__,可选实现 __str__。
5.2 比较运算:__eq__、__lt__、__hash__
python
from functools import total_ordering
@total_ordering # 只需定义 __eq__ 和 __lt__,自动生成其余比较方法
class Version:
"""语义化版本号"""
def __init__(self, major: int, minor: int, patch: int):
self.major = major
self.minor = minor
self.patch = patch
def __repr__(self) -> str:
return f"Version({self.major}, {self.minor}, {self.patch})"
def __str__(self) -> str:
return f"v{self.major}.{self.minor}.{self.patch}"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Version):
return NotImplemented # 让 Python 尝试反向操作
return (self.major, self.minor, self.patch) == \
(other.major, other.minor, other.patch)
def __lt__(self, other: object) -> bool:
if not isinstance(other, Version):
return NotImplemented
return (self.major, self.minor, self.patch) < \
(other.major, other.minor, other.patch)
def __hash__(self) -> int:
"""定义了 __eq__ 就必须定义 __hash__(否则变为不可哈希)"""
return hash((self.major, self.minor, self.patch))
v1 = Version(1, 2, 3)
v2 = Version(1, 3, 0)
v3 = Version(1, 2, 3)
print(v1 < v2) # True
print(v1 == v3) # True
print(v1 >= v2) # False -- total_ordering 自动生成
print(v1 != v2) # True
print(sorted([v2, v1, Version(2, 0, 0)]))
# [Version(1, 2, 3), Version(1, 3, 0), Version(2, 0, 0)]
# 可以用作字典键和集合元素(因为实现了 __hash__)
version_features = {v1: ["feature_a"], v2: ["feature_b"]}
print(version_features[Version(1, 2, 3)]) # ['feature_a']
__eq__ 和 __hash__ 的关系:
- 如果定义了
__eq__但没定义__hash__,Python 会把__hash__设为None,对象变为不可哈希 - 不可哈希的对象不能作为 dict 的键或 set 的元素
- 规则:相等的对象必须有相同的哈希值 (
a == b则hash(a) == hash(b))
5.3 容器协议:__len__、__getitem__、__contains__
实现容器协议可以让自定义类像 list/dict 一样使用:
python
from typing import Optional, Iterator
class Playlist:
"""实现序列协议的播放列表"""
def __init__(self, name: str):
self.name = name
self._songs: list = []
def add(self, song: str) -> None:
self._songs.append(song)
# 序列协议
def __len__(self) -> int:
"""支持 len(playlist)"""
return len(self._songs)
def __getitem__(self, index):
"""支持索引和切片:playlist[0], playlist[1:3]"""
if isinstance(index, slice):
result = Playlist(f"{self.name}[slice]")
result._songs = self._songs[index]
return result
return self._songs[index]
def __setitem__(self, index: int, value: str) -> None:
"""支持赋值:playlist[0] = 'new_song'"""
self._songs[index] = value
def __delitem__(self, index: int) -> None:
"""支持删除:del playlist[0]"""
del self._songs[index]
def __contains__(self, item: str) -> bool:
"""支持 in 运算符:'song' in playlist"""
return item in self._songs
def __iter__(self) -> Iterator[str]:
"""支持 for 循环:for song in playlist"""
return iter(self._songs)
def __reversed__(self) -> Iterator[str]:
"""支持 reversed(playlist)"""
return reversed(self._songs)
def __repr__(self) -> str:
return f"Playlist(name={self.name!r}, songs={self._songs!r})"
# 使用
playlist = Playlist("我的歌单")
playlist.add("晴天")
playlist.add("夜曲")
playlist.add("七里香")
playlist.add("稻香")
print(len(playlist)) # 4
print(playlist[0]) # 晴天
print(playlist[1:3]) # Playlist(name='我的歌单[slice]', songs=['夜曲', '七里香'])
print("夜曲" in playlist) # True
print("双截棍" in playlist) # False
for song in playlist:
print(f" 播放: {song}")
# 支持 reversed
for song in reversed(playlist):
print(f" 倒放: {song}")
playlist[0] = "简单爱"
print(playlist[0]) # 简单爱
del playlist[0]
print(len(playlist)) # 3
5.4 上下文管理:__enter__ 和 __exit__
python
import time
from typing import Optional, Type
from types import TracebackType
class Timer:
"""计时器上下文管理器"""
def __init__(self, label: str = "代码块"):
self.label = label
self.elapsed: float = 0.0
def __enter__(self) -> "Timer":
self._start = time.perf_counter()
return self # 作为 as 变量的值
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType]
) -> bool:
self.elapsed = time.perf_counter() - self._start
print(f"[{self.label}] 耗时: {self.elapsed:.4f}s")
# 返回 False 表示不吞掉异常,返回 True 则吞掉
return False
# 使用
with Timer("列表推导式") as t:
data = [i ** 2 for i in range(100000)]
print(f"记录的耗时: {t.elapsed:.4f}s")
# 异常处理示例
class SuppressError:
"""吞掉指定类型的异常"""
def __init__(self, *exceptions: Type[Exception]):
self.exceptions = exceptions
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
if exc_type is not None and issubclass(exc_type, self.exceptions):
print(f"已吞掉异常: {exc_type.__name__}: {exc_val}")
return True # 吞掉异常
return False
with SuppressError(ZeroDivisionError, ValueError):
result = 1 / 0 # 不会抛出异常
print("程序继续运行") # 正常执行
# 已吞掉异常: ZeroDivisionError: division by zero
# 程序继续运行
5.5 数值运算:__add__、__radd__、__iadd__
python
class Vector:
"""二维向量,演示完整的数值运算协议"""
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
# ---------- 算术运算 ----------
def __add__(self, other):
"""v1 + v2"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
if isinstance(other, (int, float)):
return Vector(self.x + other, self.y + other)
return NotImplemented
def __radd__(self, other):
"""当左操作数不支持时调用:5 + v"""
return self.__add__(other)
def __iadd__(self, other):
"""v1 += v2(就地加法)"""
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
return self
if isinstance(other, (int, float)):
self.x += other
self.y += other
return self
return NotImplemented
def __mul__(self, scalar):
"""v * 3(标量乘法)"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""3 * v"""
return self.__mul__(scalar)
# ---------- 一元运算 ----------
def __neg__(self):
"""-v"""
return Vector(-self.x, -self.y)
def __abs__(self):
"""abs(v) 返回向量的模"""
return (self.x ** 2 + self.y ** 2) ** 0.5
# ---------- 布尔值 ----------
def __bool__(self) -> bool:
"""bool(v):零向量为 False"""
return self.x != 0 or self.y != 0
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(v1 + 10) # Vector(13, 14)
print(10 + v1) # Vector(13, 14) -- 触发 __radd__
print(v1 * 3) # Vector(9, 12)
print(3 * v1) # Vector(9, 12) -- 触发 __rmul__
print(-v1) # Vector(-3, -4)
print(abs(v1)) # 5.0 -- 勾股定理
print(bool(v1)) # True
print(bool(Vector(0, 0))) # False
v3 = Vector(1, 1)
v3 += v2 # 触发 __iadd__
print(v3) # Vector(2, 3)
__add__ vs __radd__ vs __iadd__ 调用时机:
v1 + v2 → v1.__add__(v2)
如果返回 NotImplemented → v2.__radd__(v1)
5 + v1 → (5).__add__(v1) → NotImplemented
→ v1.__radd__(5)
v1 += v2 → v1.__iadd__(v2)
如果没有 __iadd__ → v1 = v1.__add__(v2)
5.6 魔法方法速查表
| 类别 | 方法 | 触发方式 | 用途 |
|---|---|---|---|
| 构造 | __new__ |
MyClass() |
创建实例 |
__init__ |
MyClass() |
初始化实例 | |
__del__ |
垃圾回收时 | 析构(少用) | |
| 表示 | __repr__ |
repr(obj) |
开发者表示 |
__str__ |
str(obj), print(obj) |
用户表示 | |
__format__ |
format(obj), f"{obj}" |
格式化 | |
__bytes__ |
bytes(obj) |
字节表示 | |
| 比较 | __eq__ / __ne__ |
== / != |
相等判断 |
__lt__ / __le__ |
< / <= |
大小比较 | |
__gt__ / __ge__ |
> / >= |
大小比较 | |
__hash__ |
hash(obj) |
哈希值 | |
| 容器 | __len__ |
len(obj) |
长度 |
__getitem__ |
obj[key] |
索引/键访问 | |
__setitem__ |
obj[key] = val |
索引/键赋值 | |
__delitem__ |
del obj[key] |
索引/键删除 | |
__contains__ |
item in obj |
成员检测 | |
__iter__ |
for x in obj |
迭代 | |
__next__ |
next(obj) |
迭代器 | |
__reversed__ |
reversed(obj) |
反向迭代 | |
| 数值 | __add__ / __radd__ |
a + b / b + a |
加法 |
__sub__ / __rsub__ |
a - b |
减法 | |
__mul__ / __rmul__ |
a * b |
乘法 | |
__truediv__ |
a / b |
除法 | |
__floordiv__ |
a // b |
整除 | |
__mod__ |
a % b |
取模 | |
__pow__ |
a ** b |
幂运算 | |
__neg__ / __pos__ |
-a / +a |
一元运算 | |
__abs__ |
abs(a) |
绝对值 | |
| 上下文 | __enter__ |
with obj as x |
进入 |
__exit__ |
with 块结束时 |
退出 | |
| 属性 | __getattr__ |
obj.missing_attr |
属性未找到时 |
__getattribute__ |
obj.any_attr |
所有属性访问 | |
__setattr__ |
obj.attr = val |
属性赋值 | |
__delattr__ |
del obj.attr |
属性删除 | |
| 可调用 | __call__ |
obj() |
实例作为函数 |
| 布尔 | __bool__ |
bool(obj), if obj |
真值判断 |
六、面试高频题详解
Q1:__new__ 和 __init__ 的区别是什么?
python
# 一句话:__new__ 创建对象,__init__ 初始化对象
# __new__ 是类方法(第一个参数 cls),负责分配内存并返回实例
# __init__ 是实例方法(第一个参数 self),负责设置属性
# 关键点:
# 1. __new__ 先于 __init__ 执行
# 2. __new__ 必须返回一个实例,否则 __init__ 不会被调用
# 3. 通常只在以下场景重写 __new__:
# - 实现单例模式
# - 继承不可变类型(int, str, tuple)
# - 控制实例创建过程(如对象池)
# 单例模式面试手写
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name: str = "default"):
self.name = name
s1 = Singleton("first")
s2 = Singleton("second")
print(s1 is s2) # True
print(s1.name) # second -- 注意 __init__ 每次都会执行
Q2:解释 Python 的 MRO 机制
python
# MRO(Method Resolution Order)是 Python 在多重继承场景下
# 确定方法查找顺序的机制,使用 C3 线性化算法。
# 核心规则:
# 1. 子类在父类之前
# 2. 声明顺序决定优先级(class D(B, C):B 优先于 C)
# 3. 公共基类在所有子类之后
class A:
def who(self) -> str:
return "A"
class B(A):
def who(self) -> str:
return "B"
class C(A):
def who(self) -> str:
return "C"
class D(B, C):
pass # 不重写 who
# MRO: D -> B -> C -> A -> object
print(D().who()) # B
print(D.__mro__)
# super() 遵循 MRO 链,不是简单调用"父类"
class E(B, C):
def who(self) -> str:
# super() 调用的是 MRO 链中的下一个,即 B
return f"E -> {super().who()}"
print(E().who()) # E -> B
Q3:什么是鸭子类型?与显式接口有什么区别?
python
# 鸭子类型:不关心对象的类型,只关心它是否有所需的方法/属性
# "If it quacks like a duck, it is a duck"
# 鸭子类型:任何有 read 方法的对象都可以传入
def read_data(source):
return source.read()
# 无需继承任何基类,只要有 read 方法即可
class FileSource:
def read(self) -> str:
return "文件数据"
class NetworkSource:
def read(self) -> str:
return "网络数据"
class StringSource:
def read(self) -> str:
return "字符串数据"
for src in [FileSource(), NetworkSource(), StringSource()]:
print(read_data(src))
# 对比显式接口(ABC):必须继承基类
from abc import ABC, abstractmethod
class DataSource(ABC):
@abstractmethod
def read(self) -> str:
pass
class TypedFileSource(DataSource):
def read(self) -> str:
return "文件数据"
# 对比 Protocol(Python 3.8+):结构化子类型,兼顾两者优势
from typing import Protocol
class Readable(Protocol):
def read(self) -> str: ...
def read_data_typed(source: Readable) -> str:
"""mypy 会检查传入对象是否有 read 方法,但不要求继承"""
return source.read()
| 方式 | 优点 | 缺点 |
|---|---|---|
| 鸭子类型 | 灵活、无侵入 | 运行时才报错 |
| ABC | 强约束、文档性强 | 需要继承,侵入性 |
| Protocol | 兼顾灵活和类型安全 | 需要 Python 3.8+ |
Q4:组合和继承各自的适用场景是什么?
python
# 继承:is-a 关系
# - 子类确实"是"父类的一种特化
# - 需要利用多态(如框架要求继承特定基类)
# - 子类需要修改父类的行为(方法重写)
# 典型正确使用继承的场景
from abc import ABC, abstractmethod
class Serializer(ABC):
@abstractmethod
def serialize(self, data: dict) -> str:
pass
class JsonSerializer(Serializer): # JsonSerializer IS-A Serializer
def serialize(self, data: dict) -> str:
import json
return json.dumps(data)
class XmlSerializer(Serializer): # XmlSerializer IS-A Serializer
def serialize(self, data: dict) -> str:
items = "".join(f"<{k}>{v}</{k}>" for k, v in data.items())
return f"<root>{items}</root>"
# 组合:has-a 关系
# - 功能复用但不存在"是一种"关系
# - 需要更灵活地替换组件
# - 避免深继承链
class Logger:
def log(self, msg: str) -> None:
print(f"[LOG] {msg}")
class Cache:
def __init__(self):
self._store: dict = {}
def get(self, key: str):
return self._store.get(key)
def set(self, key: str, value) -> None:
self._store[key] = value
class ApiService:
"""ApiService HAS-A Logger and HAS-A Cache"""
def __init__(self, logger: Logger, cache: Cache):
self.logger = logger
self.cache = cache
def get_data(self, key: str) -> str:
cached = self.cache.get(key)
if cached:
self.logger.log(f"缓存命中: {key}")
return cached
self.logger.log(f"缓存未命中: {key}, 从数据库读取")
value = f"data_for_{key}"
self.cache.set(key, value)
return value
service = ApiService(Logger(), Cache())
print(service.get_data("user_1"))
print(service.get_data("user_1"))
# [LOG] 缓存未命中: user_1, 从数据库读取
# data_for_user_1
# [LOG] 缓存命中: user_1
# data_for_user_1
本章总结
本文从底层机制到设计原则,系统性地剖析了 Python 面向对象编程的高级特性:
-
类的两阶段构造 :
__new__负责创建实例(分配内存),__init__负责初始化属性。重写__new__的典型场景是单例模式和不可变类型扩展。 -
MRO 与
super():Python 使用 C3 线性化算法确定多重继承的方法查找顺序。super()不是"调用父类",而是调用 MRO 链中的下一个类,这是协作式多重继承的基础。 -
Python 的封装与多态 :名称改编(
__name)防止子类意外覆盖,但不是安全机制。鸭子类型是 Python 多态的核心哲学,Protocol 提供了鸭子类型的静态检查能力。 -
SOLID 原则:在 Python 中,SRP 通过职责拆分实现,OCP 通过抽象基类/Protocol 实现,LSP 要求子类完全替代父类,ISP 通过细粒度 Protocol 实现,DIP 通过构造函数注入实现。
-
组合优于继承:继承表示"is-a"关系,组合表示"has-a"关系。Mixin 是多重继承的特殊用法,提供可复用的功能片段。优先选择组合,只在真正的"is-a"场景使用继承。
-
魔法方法 :通过实现
__repr__、__eq__、__hash__、__len__、__getitem__、__enter__/__exit__、__add__等协议方法,自定义类可以无缝融入 Python 生态。
核心原则 :Python 的 OOP 不是 Java 式的"一切皆对象"强制范式,而是提供工具让你在需要时使用。简单场景用函数和字典,中等复杂度用 dataclass,复杂系统才用完整的 OOP 设计。不要为了 OOP 而 OOP。
下一篇预告
第 04 篇:Python 内存管理与垃圾回收 -- 从引用计数到分代回收
下一篇文章将深入 Python 内存管理的底层世界。你将了解:
- Python 对象的内存布局:PyObject 和 PyVarObject 的结构,小整数缓存池(-5~256),字符串驻留机制
- 引用计数机制 :引用计数的增减规则,
sys.getrefcount()的使用,循环引用的问题 - 分代垃圾回收:Generation 0/1/2 的分代策略,标记-清除算法如何解决循环引用
- 内存优化实战 :
__slots__的原理与限制,生成器 vs 列表的内存效率,tracemalloc排查内存泄漏 - CPython 内存分配器:pymalloc 小对象分配器,Arena/Pool/Block 三级结构,Python 进程"内存只增不减"的真相
理解内存管理机制不仅是面试热点,更是排查线上内存泄漏、优化大数据处理性能的必备技能。
Python 后端开发技术博客专栏 | 作者:耿雨飞
本文为专栏第 03 篇,共 25 篇。完整目录请参阅《Python技术博客专栏大纲》。