Python 基础体系 · 第 1/112 篇。示例统一以 Python 3.14 为语言基线;第三方库使用与其兼容的现代稳定版本,版本敏感行为会单独说明。
Python 完整学习路线:从语言模型、并发到 Web、数据、AI 与生产交付
先建立正确的路线观
Python 学习通常有两种失败方式。
第一种是只学语法:会写变量、循环、函数,却解释不了“为什么这个列表被另一个函数改了”“为什么 await 不能直接放在普通函数里”“为什么 Web 服务加了线程却没有变快”。
第二种是只学框架:直接进入 Web、数据分析或 AI 库,能够拼出功能,却不了解对象生命周期、异常传播、并发边界、数据库事务和进程模型。一旦需求变复杂,问题就从“不会调用 API”升级为“无法解释系统行为”。
完整路线应当按以下依赖关系推进:
flowchart LR
A[运行环境与工具] --> B[语言模型]
B --> C[数据模型与函数]
C --> D[模块 类型 测试]
D --> E[并发模型]
E --> F[Web 与网络]
E --> G[数据与数据库]
F --> H[服务化]
G --> H
H --> I[AI 应用]
H --> J[生产交付]
I --> J
这里的“语言模型”指 Python 语言的执行模型,不是大语言模型。它回答的是:
- 名称如何绑定到对象;
- 对象具有什么身份、类型和值;
- 表达式如何求值;
- 函数调用如何建立作用域;
- 异常如何传播;
- 协程如何挂起和恢复;
- 模块如何导入和初始化。
Python 3.14 的官方语言参考将这些内容组织在数据模型、执行模型、表达式、语句、导入系统和协程等章节中;语言参考强调的是语法与核心语义,具体内置类型和标准库 API 则由标准库文档描述。(docs.python.org)
因此,路线不是“先学若干库,再把它们串起来”,而是从语言语义开始,逐步建立能够支撑并发、服务、数据和交付的心智模型。
一、起点:解释器、环境和可重复运行
1. Python 程序到底如何运行
一个 Python 程序至少涉及四层:
- 源代码:
.py文件中的字符。 - 编译结果:解释器将源代码编译为代码对象,通常包含字节码。
- 运行时对象:整数、字符串、函数、类、模块等对象。
- 执行上下文:当前栈帧、局部命名空间、全局命名空间、异常状态和导入状态。
“解释执行”并不意味着 Python 每次逐字符解释源代码。实现通常会先把源代码解析并编译,再由虚拟机执行代码对象;但语言规范主要保证程序的语义,不保证某个具体解释器一定使用某种字节码或内存布局。
需要区分:
- 规范保证:例如名称绑定、异常传播、比较运算的语义。
- CPython 实现细节:例如
id()在 CPython 中通常对应对象的内存地址。 - 经验性行为:例如小整数或某些字符串可能被缓存,不能用于推断对象身份。
2. 创建项目环境
Python 的虚拟环境用于隔离项目解释器、库和脚本。官方 venv 文档明确说明,虚拟环境默认与基础 Python 安装中的第三方包隔离,环境本身应视为可删除、可重建的目录,而不是存放源代码的地方。(docs.python.org)
mkdir python-route-demo
cd python-route-demo
python3.14 -m venv .venv
source .venv/bin/activate # Linux/macOS
# Windows PowerShell:
# .venv\Scripts\Activate.ps1
python --version
python -m pip --version
预期结果类似:
Python 3.14.x
pip 25.x from .../.venv/lib/python3.14/site-packages/pip
使用 python -m pip 而不是直接使用 pip,是为了明确调用的是当前 python 对应环境中的安装工具。否则系统上可能同时存在多个 Python 和多个 pip,最终出现“安装成功但程序找不到包”的路径错配。
.venv 不应提交到 Git。交付时应提交依赖声明或锁定文件,并在目标环境重新创建虚拟环境。虚拟环境不是可移动的运行时镜像;它通常包含指向创建时基础 Python 的路径,复制到另一台机器后可能失效。(docs.python.org)
二、核心语言模型:名称不是盒子,赋值不是复制
这是整条路线中最重要的基础。
1. 对象的三个属性
Python 中的数据以对象表示。每个对象都有:
- 身份 identity:对象是哪一个对象;
- 类型 type:对象支持哪些操作;
- 值 value:对象当前代表什么内容。
对象身份一旦创建就不会改变;对象类型也不会改变;对象的值则可能可变或不可变。列表和字典通常是可变对象,整数、字符串和元组通常是不可变对象。(docs.python.org)
可以用一个关系表示名称绑定:
其中:
- 是命名空间中的名称;
- 是对象;
- 箭头表示名称当前绑定到对象。
名称不是装值的盒子。执行:
x = 10
不是把整数 10 放进名为 x 的容器,而是:
- 创建或取得一个值为
10的整数对象; - 在当前命名空间中建立绑定
x -> 该对象。
执行:
x = 20
则是把 x 重新绑定到另一个整数对象,原来的 10 是否销毁,取决于是否还有其他引用。
2. 赋值为什么不是复制
a = [1, 2]
b = a
b.append(3)
print(a)
print(b)
print(a is b)
输出:
[1, 2, 3]
[1, 2, 3]
True
逐步分析:
| 步骤 | a |
b |
对象内容 |
|---|---|---|---|
a = [1, 2] |
指向列表对象 L | 未绑定 | L 为 [1, 2] |
b = a |
指向 L | 指向 L | L 仍为 [1, 2] |
b.append(3) |
指向 L | 指向 L | L 被原地修改为 [1, 2, 3] |
b = a 只增加了一个名称绑定,不会创建新列表。
如果需要浅复制:
a = [1, 2]
b = a.copy()
b.append(3)
print(a) # [1, 2]
print(b) # [1, 2, 3]
print(a is b) # False
但浅复制只复制最外层容器:
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]]
print(b) # [[1, 2, 99], [3, 4]]
因为:
a -> 外层列表 A -> 内层列表 X
b -> 外层列表 B -> 内层列表 X
copy() 创建了新的外层列表 B,但内层列表 X 仍然共享。需要递归复制时才考虑 copy.deepcopy(),但深拷贝可能带来更高成本、无法复制某些资源对象,且不能替代清晰的数据所有权设计。
3. 可变与不可变的真正边界
x = 10
y = x
x += 1
print(x) # 11
print(y) # 10
print(x is y) # 通常为 False,但不应依赖身份
整数不可变,因此 x += 1 的效果近似于:
x = x + 1
它创建或取得了一个新整数对象,并让 x 重新绑定。
列表则不同:
x = [10]
y = x
x += [20]
print(x) # [10, 20]
print(y) # [10, 20]
对列表而言,+= 通常执行原地扩展,等价效果接近:
x.extend([20])
这也是为什么“+= 一定创建新对象”是错误的说法。
4. 不可变容器也可能观察到内部变化
items = ([1, 2], "stable")
items[0].append(3)
print(items) # ([1, 2, 3], 'stable')
元组本身不可变:它不能把第一个元素替换成另一个对象。但第一个元素指向的列表可变,因此列表内容仍然可以改变。官方数据模型也特别区分了“容器本身不可变”和“容器内对象不可变”。(docs.python.org)
5. 函数参数是“共享对象引用”
def add_item(values: list[int]) -> None:
values.append(3)
data = [1, 2]
add_item(data)
print(data) # [1, 2, 3]
函数接收到的是一个新的局部名称 values,它与调用方的 data 指向同一个列表对象:
调用方: data ─┐
├──> 列表 L
函数内: values ─┘
如果函数重新绑定参数,不会影响调用方:
def replace(values: list[int]) -> None:
values = [99]
data = [1, 2]
replace(data)
print(data) # [1, 2]
这里发生的是:
values最初指向列表 L;values = [99]让values指向新列表 M;data仍然指向 L。
因此应区分:
- 修改对象:可能被其他引用观察到;
- 重新绑定名称:只影响该名称所在的作用域。
6. 默认参数的反例
默认参数在函数定义时求值,而不是每次调用时求值:
def append_value(value: int, bucket: list[int] = []) -> list[int]:
bucket.append(value)
return bucket
print(append_value(1)) # [1]
print(append_value(2)) # [1, 2]
两次调用共享同一个默认列表对象。
正确写法:
def append_value(value: int, bucket: list[int] | None = None) -> list[int]:
if bucket is None:
bucket = []
bucket.append(value)
return bucket
None 在这里不是业务数据,而是“调用者没有提供容器”的哨兵。若业务上允许明确传入 None,则应使用专门的哨兵对象:
_MISSING = object()
def read(value=_MISSING):
if value is _MISSING:
return "not provided"
return value
7. 名称解析与闭包
Python 的局部、闭包、全局和内置名称解析通常可概括为 LEGB:
- Local:当前函数局部作用域;
- Enclosing:外层函数作用域;
- Global:模块全局作用域;
- Builtins:内置名称作用域。
count = 10
def outer():
count = 20
def inner():
return count
return inner
f = outer()
print(f()) # 20
inner() 没有局部 count,因此从外层函数闭包中读取 count = 20。
如果要修改外层绑定,必须使用 nonlocal:
def counter():
value = 0
def increment():
nonlocal value
value += 1
return value
return increment
next_value = counter()
print(next_value()) # 1
print(next_value()) # 2
闭包保存的是外层变量的绑定单元,而不是调用时重新查找一个普通局部变量。理解这一点,才能正确使用装饰器、工厂函数、回调和异步任务。
三、从语法到可维护程序:必须掌握的语言主干
1. 表达式、语句和求值顺序
表达式会产生对象或值:
total = price * quantity
语句改变程序状态:
if total > 100:
discount = 0.9
学习时不能只记运算符优先级,还要理解求值顺序。函数参数、容器字面量和表达式通常按照从左到右的顺序求值,但赋值目标的处理、短路逻辑和生成器表达式有各自规则。
def mark(name):
print(name)
return name
result = mark("left") + mark("right")
输出顺序是:
left
right
短路逻辑则可能跳过右侧表达式:
False and mark("never")
True or mark("also never")
这不仅是语法知识,也直接影响条件判断、缓存读取和异常触发。
2. 基础数据结构要按数据约束选择
| 类型 | 核心约束 | 典型用途 |
|---|---|---|
list |
有序、可变、允许重复 | 批量数据、队列原型 |
tuple |
有序、不可变 | 固定结构、记录、键 |
dict |
键到值的映射,保留插入顺序 | 配置、索引、JSON 数据 |
set |
唯一元素、无索引 | 去重、集合运算、成员测试 |
str |
Unicode 文本 | 人类可读内容 |
bytes |
不可变字节序列 | 网络协议、文件和序列化 |
字典键必须保持稳定的哈希值,因此列表和字典不能直接作为键。1 与 1.0 比较相等,在字典和集合中也会按照相等与哈希规则视作同一个键类别。(docs.python.org)
data = {
"user_id": 42,
"roles": {"admin", "auditor"},
}
payload = '{"ok": true}'
raw = payload.encode("utf-8")
text = raw.decode("utf-8")
不要把 str 与 bytes 混用。文本需要编码成字节才能写入网络或文件,字节需要根据明确编码才能还原为文本。
3. 迭代器、生成器和惰性计算
可迭代对象能产生迭代器;迭代器每次提供一个元素,并在耗尽时抛出 StopIteration。
生成器函数使用 yield,调用时不会立刻执行完整函数体,而是返回生成器对象;每次 next() 才推进到下一个 yield。(docs.python.org)
def numbers():
print("start")
yield 1
print("middle")
yield 2
it = numbers()
print("created")
print(next(it))
print(next(it))
输出:
created
start
1
middle
2
生成器适合处理无法一次性装入内存的数据:
def read_lines(path: str):
with open(path, encoding="utf-8") as file:
for line in file:
yield line.rstrip("\n")
但惰性并不等于免费:
- 生成器只降低一次性内存占用;
- 下游如果调用
list(generator),仍会一次性物化; - 生成器内部异常会在迭代阶段而不是创建阶段出现;
- 文件、数据库游标等资源必须在正确生命周期内关闭。
4. 函数、类和协议
函数学习顺序应是:
- 参数绑定;
- 返回值;
- 作用域;
- 高阶函数;
- 装饰器;
- 上下文管理器;
- 异步函数。
类不应先从“继承层次”开始,而应先理解对象协议。Python 中很多行为通过特殊方法实现:
class User:
def __init__(self, name: str):
self.name = name
def __repr__(self) -> str:
return f"User(name={self.name!r})"
def __eq__(self, other: object) -> bool:
return isinstance(other, User) and self.name == other.name
__repr__ 决定调试表示,__eq__ 定义相等关系。若定义相等却没有正确处理哈希,实例放入集合或作为字典键时就可能违反数据结构约束。
上下文管理器则把资源获取与释放绑定起来:
with open("output.txt", "w", encoding="utf-8") as file:
file.write("hello")
即使 write() 抛出异常,退出 with 块时也会执行关闭逻辑。对象不可达后何时被垃圾回收并不是语言层面的资源释放保证;官方文档明确建议显式关闭文件等外部资源。(docs.python.org)
5. 异常不是返回值的替代品
异常表示当前控制流无法按正常结果继续。
def parse_port(value: str) -> int:
try:
port = int(value)
except ValueError as exc:
raise ValueError(f"invalid port: {value!r}") from exc
if not 1 <= port <= 65535:
raise ValueError("port must be between 1 and 65535")
return port
这里有三层语义:
int(value)可能抛出ValueError;- 原始异常通过
from exc保存为因果链; - 业务层抛出更适合调用者理解的新异常。
错误处理应回答:
- 哪一层负责记录日志;
- 哪一层负责重试;
- 哪一层负责转成 HTTP 状态码;
- 哪些错误可以恢复;
- 哪些错误必须终止当前任务。
一个常见错误是:
try:
do_work()
except Exception:
pass
它会丢失故障信号,使任务看似成功、数据却已经不完整。捕获异常必须有明确动作,例如转换、补偿、重试、记录后重新抛出,或安全降级。
6. 模块和导入
模块是代码组织单位,也是一个命名空间。模块属性访问本质上是模块命名空间中的查找;模块导入还涉及查找、加载、初始化和缓存。(docs.python.org)
# app/config.py
TIMEOUT = 3
# app/main.py
from .config import TIMEOUT
应理解以下边界:
import package.module导入模块对象;from package.module import name把名称绑定到当前模块;- 导入时会执行模块顶层代码;
- 导入结果会被缓存,重复导入通常不会重新执行;
- 循环导入可能访问到“尚未初始化完成”的模块。
因此,模块顶层应尽量只放定义和稳定初始化,不要在导入时执行数据库迁移、启动线程或发起网络请求。
四、类型、测试和工具:把“能运行”变成“可演进”
1. 类型标注是接口描述,不是运行时验证
def total(items: list[int]) -> int:
return sum(items)
这个标注告诉阅读者和静态检查器:函数期望整数列表并返回整数。它不会自动阻止调用者传入错误对象,也不替代运行时校验。
对外部输入必须显式验证:
def parse_user(payload: dict[str, object]) -> tuple[int, str]:
user_id = payload.get("id")
name = payload.get("name")
if not isinstance(user_id, int):
raise ValueError("id must be int")
if not isinstance(name, str) or not name:
raise ValueError("name must be non-empty string")
return user_id, name
Python 3.14 的函数注解采用惰性求值机制,函数对象还提供了与注解相关的新属性;这属于版本敏感行为,依赖注解运行时对象的工具应针对 3.14 文档和实际解释器进行验证。(docs.python.org)
2. 测试要覆盖状态和失败路径
一个完整测试至少包含:
- 正常输入;
- 边界输入;
- 非法输入;
- 外部依赖失败;
- 重试或超时;
- 并发取消;
- 数据库事务回滚。
例如价格计算:
from decimal import Decimal
def calculate_total(
price: Decimal,
quantity: int,
) -> Decimal:
if price < 0:
raise ValueError("price must not be negative")
if quantity < 0:
raise ValueError("quantity must not be negative")
return price * quantity
测试不应只验证 2 * 3 == 6,还应验证负数被拒绝,并明确金额使用 Decimal 而不是二进制浮点数的业务边界。
3. 工具链的学习顺序
建议掌握:
python -m venv
python -m pip
pytest
ruff 或同类检查器
静态类型检查器
覆盖率工具
调试器与 profiling 工具
工具不是独立目标。每个工具都应服务于一个可验证的问题:
- 依赖是否可重建;
- 类型边界是否一致;
- 异常路径是否覆盖;
- 热点在哪里;
- 内存是否持续增长;
- 并发任务是否泄漏。
五、并发:线程、进程、解释器和协程不是同一种东西
1. 并发与并行
并发表示多个任务在时间上交错推进。
并行表示多个任务在同一时刻使用多个执行单元运行。
一个单核事件循环可以并发处理很多 I/O 任务,但同一时刻仍只能执行一个事件循环任务中的 Python 代码。asyncio 官方文档将它定位为基于 async/await 的并发库,尤其适合 I/O 密集型和高层网络代码。(docs.python.org)
选择模型前先问任务类型:
| 任务 | 首选方向 |
|---|---|
| 等待网络、磁盘、数据库 | 异步 I/O 或线程 |
| 调用阻塞库 | 线程池或进程外隔离 |
| 纯 Python CPU 计算 | 进程、解释器池,或专门的原生库 |
| 大规模独立批处理 | 进程池、任务队列 |
| 共享可变状态 | 尽量减少共享,必要时显式同步 |
2. 线程
线程共享同一进程地址空间,因此传递对象方便,但也带来竞态条件:
import threading
counter = 0
def increase():
global counter
for _ in range(100_000):
counter += 1
threads = [threading.Thread(target=increase) for _ in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(counter)
不能把 counter += 1 理解为不可分割操作。它至少包含读取、计算和写回。多个线程可能读取相同旧值,导致更新丢失。
import threading
counter = 0
lock = threading.Lock()
def increase():
global counter
for _ in range(100_000):
with lock:
counter += 1
锁保证临界区互斥,但不能自动解决死锁、锁粒度过大和任务饥饿。更好的设计通常是减少共享状态,让线程通过消息或队列传递数据。
3. 进程
进程拥有独立地址空间,通信需要序列化、管道、队列、共享内存或外部服务。它可以绕开传统 CPython 全局解释器锁对多进程的限制,但进程创建和数据传输都有成本。multiprocessing 通过子进程提供与 threading 相似的 API,并支持进程池。(docs.python.org)
from concurrent.futures import ProcessPoolExecutor
def square(value: int) -> int:
return value * value
if __name__ == "__main__":
with ProcessPoolExecutor() as pool:
print(list(pool.map(square, range(5))))
输出:
[0, 1, 4, 9, 16]
必须使用 if __name__ == "__main__": 保护入口,特别是使用 spawn 时,子进程需要重新导入主模块。如果顶层代码直接创建新进程,可能无限递归创建子进程。
Python 3.14 中,ProcessPoolExecutor 默认进程启动方式已不再是 fork;如果确实需要 fork,应显式传入对应的 multiprocessing context。不同平台的默认方式和对全局可变状态的继承行为不同,不能把某个 Linux 环境的行为当成跨平台保证。(docs.python.org)
4. 解释器池
Python 3.14 的 InterpreterPoolExecutor 使用多个解释器,每个解释器运行在线程中,但拥有独立的解释器状态和全局解释器锁,因此可以实现多核并行。不同解释器之间不能直接共享可变 Python 对象,通信通常需要序列化或专用机制。(docs.python.org)
from concurrent.futures import InterpreterPoolExecutor
def compute(value: int) -> int:
return value * value
if __name__ == "__main__":
with InterpreterPoolExecutor(max_workers=2) as pool:
results = list(pool.map(compute, range(6)))
print(results)
它的关键取舍是:
- 比线程更适合需要多核的 Python 计算;
- 比进程少一些进程级隔离成本;
- 仍然要求任务和参数满足跨解释器传递约束;
- 不能依赖“共享模块全局变量”来传递状态。
这是 Python 3.14 的版本敏感能力,使用前应验证依赖库是否支持多解释器环境。
六、asyncio:事件循环、协程、Future 和调度
1. 协程对象不是任务
import asyncio
async def fetch() -> str:
await asyncio.sleep(1)
return "ok"
coroutine = fetch()
print(type(coroutine))
调用 async def 函数会得到协程对象;它尚未执行函数体。协程对象需要被:
await;- 包装成
Task; - 交给事件循环运行。
最小完整程序:
import asyncio
async def fetch(name: str, delay: float) -> str:
await asyncio.sleep(delay)
return name
async def main() -> None:
first, second = await asyncio.gather(
fetch("first", 0.2),
fetch("second", 0.1),
)
print(first, second)
asyncio.run(main())
输出:
first second
两个任务的完成顺序是 second 先完成,但 gather() 按传入顺序返回结果,所以打印顺序仍是 first second。
2. 事件循环的状态机
事件循环可以抽象为:
就绪队列 -> 执行协程一步 -> 遇到 await
|
v
等待 I/O 或定时器
|
v
条件满足
|
v
就绪队列
一次循环通常做这些事:
- 取出可运行的回调或任务;
- 推进任务执行;
- 如果任务遇到
await,保存其暂停位置; - 注册 I/O、定时器或 Future 完成条件;
- 等待操作系统通知;
- 条件满足后把任务重新放入就绪队列。
协作式调度的关键是:任务必须主动让出控制权。
async def bad():
import time
time.sleep(5) # 阻塞整个事件循环
time.sleep() 不会让出事件循环,它会阻塞当前线程。正确写法:
async def good():
await asyncio.sleep(5)
如果必须调用阻塞函数:
import asyncio
import time
def blocking_work() -> str:
time.sleep(1)
return "done"
async def main() -> None:
result = await asyncio.to_thread(blocking_work)
print(result)
asyncio.run(main())
这并不会把 CPU 计算神奇地变成并行;它的主要用途是把阻塞调用移出事件循环线程。
3. Task 和 Future
Future 表示一个尚未完成的结果容器,状态通常经历:
PENDING -> RUNNING -> FINISHED
\-> CANCELLED
Task 是事件循环对协程的调度包装,通常也是 Future 的一种扩展形式。
import asyncio
async def work() -> int:
await asyncio.sleep(0.1)
return 42
async def main() -> None:
task = asyncio.create_task(work())
print(task.done()) # False
result = await task
print(result) # 42
print(task.done()) # True
asyncio.run(main())
concurrent.futures.Future 与 asyncio.Future 名称相同,但属于不同并发体系。前者用于线程池、进程池或解释器池;后者用于事件循环中的异步任务。官方文档明确提醒二者不能混淆。(docs.python.org)
4. 取消、超时和异常
取消不是强制杀死线程,而是向协程注入取消信号。协程应在 await 等可中断点处理它。
import asyncio
async def worker() -> None:
try:
while True:
print("working")
await asyncio.sleep(0.2)
except asyncio.CancelledError:
print("cleanup")
raise
async def main() -> None:
task = asyncio.create_task(worker())
await asyncio.sleep(0.5)
task.cancel()
try:
await task
except asyncio.CancelledError:
print("cancelled")
asyncio.run(main())
输出可能类似:
working
working
working
cleanup
cancelled
如果捕获 CancelledError 后不重新抛出,调用者可能误以为任务正常完成。因此只有在确实完成清理并有明确语义时才吞掉取消。
超时示例:
import asyncio
async def request() -> str:
await asyncio.sleep(10)
return "response"
async def main() -> None:
try:
result = await asyncio.wait_for(request(), timeout=1)
print(result)
except TimeoutError:
print("timeout")
asyncio.run(main())
生产代码还必须考虑:
- 超时后底层操作是否真的停止;
- 重试是否造成重复写入;
- 多个任务中一个失败时是否取消其他任务;
- 取消时连接、文件、锁和临时数据是否释放。
5. 结构化并发
任务应当有清晰的父子生命周期。Python 3.11 起提供的任务组适合表达“这一组任务共同完成一个工作单元”的关系:
import asyncio
async def fetch(name: str) -> str:
await asyncio.sleep(0.1)
if name == "bad":
raise RuntimeError("fetch failed")
return name
async def main() -> None:
try:
async with asyncio.TaskGroup() as group:
first = group.create_task(fetch("first"))
bad = group.create_task(fetch("bad"))
except* RuntimeError as exc:
print(f"failed: {exc.exceptions[0]}")
asyncio.run(main())
任务组中的异常会影响整个任务组的退出,避免后台任务无人管理。相比随意创建大量 create_task(),结构化生命周期更容易保证异常、取消和资源释放的一致性。
Python 3.14 还为自由线程化 Python 提供了 asyncio 相关支持,但事件循环中的单个任务仍然需要避免长时间运行的同步 Python 代码;一个事件循环在一个核心上可以管理大量连接,但每个请求中的计算仍会占用执行时间。(docs.python.org)
七、Web:从 HTTP 到应用生命周期
1. 先理解 HTTP 数据流
一次典型请求包含:
客户端
-> TCP/TLS
-> HTTP 请求行、请求头、请求体
-> Web 服务器
-> Python 应用
-> 路由
-> 业务逻辑
-> 数据库或外部服务
-> HTTP 响应
-> 客户端
应用不应把所有输入都当成可信数据。至少要验证:
- HTTP 方法;
- 路径参数;
- 查询参数;
- 请求体结构;
- 内容类型;
- 身份和权限;
- 超时和大小限制。
2. WSGI 与 ASGI
WSGI 是传统同步 Python Web 应用与服务器之间的接口模型。
ASGI 扩展了同步请求模型,能够表达异步 HTTP、WebSocket 和生命周期事件。选择 ASGI 不等于业务代码自动异步;如果在 async def 处理器中调用阻塞数据库驱动或同步 HTTP 客户端,仍会阻塞事件循环。
一个框架的完整生命周期应理解为:
进程启动
-> 加载配置
-> 创建连接池
-> 注册路由和中间件
-> 接收请求
-> 解析与验证
-> 执行业务逻辑
-> 提交或回滚事务
-> 返回响应
-> 进程退出
-> 关闭连接池
框架只负责一部分生命周期。数据库连接、后台任务、缓存客户端和模型对象是否正确创建、复用和关闭,仍然是应用设计责任。
3. 一个最小 ASGI 应用
不依赖第三方框架也可以理解 ASGI 的基本形状:
from typing import Any
async def application(
scope: dict[str, Any],
receive: Any,
send: Any,
) -> None:
if scope["type"] != "http":
return
await send({
"type": "http.response.start",
"status": 200,
"headers": [(b"content-type", b"text/plain; charset=utf-8")],
})
await send({
"type": "http.response.body",
"body": b"hello",
})
ASGI 通过事件消息传递请求和响应,而不是把所有细节固定成单一函数返回值。真实框架会在外层处理路由、请求体、异常、中间件和连接生命周期。
4. Web 业务的错误边界
业务错误不应全部变成 500:
| 错误 | 常见响应 |
|---|---|
| JSON 格式错误 | 400 |
| 未认证 | 401 |
| 无权限 | 403 |
| 资源不存在 | 404 |
| 请求冲突 | 409 |
| 参数语义非法 | 422 或 400 |
| 未预期程序异常 | 500 |
但状态码只是传输层表达。真正重要的是内部是否保留异常因果、请求 ID、用户身份、数据库事务状态和外部调用结果。
八、数据:从内存结构到数据库一致性
1. SQL 不是“把列表存起来”
数据库学习应从关系和约束开始:
- 表表示关系;
- 行表示实体或事实;
- 列表示属性;
- 主键标识记录;
- 外键表达引用;
- 唯一约束表达业务不重复;
- 检查约束表达局部不变量;
- 事务表达一组操作的原子边界。
例如订单扣库存:
BEGIN;
UPDATE inventory
SET available = available - 1
WHERE product_id = 100
AND available >= 1;
-- 检查受影响行数必须为 1
INSERT INTO orders (product_id, user_id)
VALUES (100, 200);
COMMIT;
如果 UPDATE 影响 0 行,说明库存不足或商品不存在,此时不能继续插入订单,应回滚。
事务的核心不是“包住几条 SQL”,而是定义状态转换:
库存 available = n
订单不存在
|
| 条件 available >= 1
v
库存 available = n - 1
订单已创建
如果应用先查库存,再更新库存,而中间没有锁、条件更新或合适的隔离机制,两个请求可能同时读到同一个库存值,形成超卖。
2. ORM 的边界
ORM 适合表达实体关系和常见 CRUD,但不应隐藏:
- SQL 查询数量;
- 事务开始和结束;
- 锁;
- 延迟加载;
- 批量操作;
- 索引使用;
- 数据库错误。
一个循环中逐条访问关联对象,可能产生 N+1 查询。诊断方法是打开 SQL 日志、检查执行计划,并确认请求一次执行了多少条查询,而不是只看 Python 代码行数。
3. 数据处理路线
数据方向建议按以下顺序:
Python 容器与迭代器
-> CSV、JSON、文本和二进制
-> SQL 与事务
-> NumPy 数组与向量化
-> DataFrame 与缺失值、分组、连接
-> ETL / ELT
-> 数据质量、血缘和调度
数据处理最容易出现的错误不是语法错误,而是语义错误:
- 时区混乱;
- 金额用浮点数;
- 缺失值与零混淆;
- 字符串数字未转换;
- 重复记录未定义;
- 连接键不唯一导致行数膨胀;
- 统计口径未固定。
学习每个数据工具时,都要回答“输入的模式是什么、输出的模式是什么、缺失数据如何传播、失败后是否可重跑”。
九、AI:从数值计算到可验证的应用系统
AI 不是 Python 语言的一个语法分支,而是建立在数据、数值计算、模型接口和生产服务之上的应用层。
1. 先补齐数学和数据基础
至少需要理解:
- 向量、矩阵和张量;
- 线性变换;
- 概率分布;
- 损失函数;
- 梯度下降;
- 训练集、验证集和测试集;
- 过拟合;
- 推理延迟与吞吐量。
一个简单的线性模型:
其中:
- 是输入;
- 是权重;
- 是偏置;
- 是预测值。
若使用平方损失:
梯度为:
学习率为 时,参数更新为:
完整数值算例:
x = 2
y = 10
w = 3
b = 1
η = 0.1
第一步预测:
第二步误差:
第三步梯度:
第四步更新:
第五步重新预测:
预测从 7 接近了 10。这个例子虽然简单,却说明了“模型、损失、梯度、参数更新”之间的因果链。
2. 训练和推理是不同系统
训练关注:
- 数据读取;
- 批处理;
- 梯度计算;
- 参数更新;
- checkpoint;
- 验证指标;
- 可复现性。
推理关注:
- 模型加载;
- 输入校验;
- 批量与并发;
- 延迟;
- 显存或内存;
- 超时;
- 降级;
- 输出安全。
训练阶段的代码不能直接原样搬到 Web 请求中。模型加载一次还是每请求加载、是否共享只读权重、是否需要进程级副本,都会改变容量和内存模型。
3. LLM 应用的最小闭环
大语言模型应用通常包含:
用户输入
-> 输入校验与安全过滤
-> 上下文检索或工具调用
-> Prompt 组装
-> 模型推理
-> 输出解析
-> 业务校验
-> 持久化与观测
如果使用检索增强生成,至少要区分:
- 文档切分;
- 嵌入生成;
- 向量检索;
- 候选重排;
- 上下文拼接;
- 模型生成;
- 引用或证据绑定。
“模型输出看起来合理”不是正确性的证明。应建立评估集,检查:
- 检索是否找到相关内容;
- 生成是否引用了上下文;
- 结构化输出是否符合 schema;
- 敏感信息是否泄漏;
- 失败时是否明确拒答;
- 模型升级后指标是否回归。
AI 应用交付的核心不是把模型接上 API,而是让输入、上下文、模型版本、输出和评估结果可追踪。
十、生产交付:进程模型、容量、配置、迁移、灰度和回滚
1. 进程模型
一个 Python Web 服务通常可以分成:
负载均衡器
|
多个服务进程
|
每个进程:
路由与业务代码
数据库连接池
缓存连接
本地内存缓存
线程池或事件循环
必须区分:
- 进程级状态:每个 worker 一份;
- 线程级状态:同一进程内共享地址空间;
- 协程级状态:任务之间应通过局部变量或上下文隔离;
- 外部共享状态:数据库、缓存、消息队列。
如果进程内有本地缓存,多个 worker 之间不会自动同步。一个请求写入 worker A 的缓存,随后请求落到 worker B,可能仍读到旧值。因此一致性要求高的状态应放在共享存储中。
2. 容量估算
对于同步服务,可以用近似关系理解并发:
其中:
- 是平均在途请求数;
- 是吞吐率,单位为请求/秒;
- 是平均响应时间,单位为秒。
例如:
R = 200 请求/秒
T = 0.25 秒
C ≈ 200 × 0.25 = 50 个在途请求
如果数据库连接池只有 10 个连接,而每个请求都需要占用数据库连接,那么数据库可能成为瓶颈,即使 Web worker 还能接收更多请求。
容量评估不能只看 CPU。还要看:
- 内存;
- 数据库连接数;
- 外部 API 限流;
- 文件描述符;
- 事件循环阻塞时间;
- 队列长度;
- 单请求数据量;
- 模型显存或进程内存。
3. 配置
配置应按生命周期和敏感性分类:
| 配置 | 示例 | 处理方式 |
|---|---|---|
| 代码默认值 | 超时默认值 | 可进入代码 |
| 环境配置 | 数据库地址 | 部署注入 |
| 密钥 | API Key、密码 | 密钥管理系统 |
| 运行时开关 | 灰度比例 | 可动态读取或配置中心 |
| 数据库结构 | 表和索引 | 迁移系统管理 |
不要把密钥写入源码、镜像层或日志。配置解析应在启动时完成,并对必需项进行失败即停的校验:
import os
def require_env(name: str) -> str:
value = os.getenv(name)
if not value:
raise RuntimeError(f"missing required environment variable: {name}")
return value
DATABASE_URL = require_env("DATABASE_URL")
启动阶段失败比服务启动后收到第一条请求才失败更容易发现,也更容易阻止错误版本进入流量。
4. 数据库迁移
迁移是对数据库结构的有序状态转换:
Schema v1
-> migration 001
Schema v2
-> migration 002
Schema v3
生产迁移必须考虑旧代码和新代码可能同时运行。因此优先使用 扩展—迁移—收缩:
- 扩展:新增可选列或新表;
- 发布兼容代码:同时读写旧字段和新字段;
- 回填数据;
- 切换读取逻辑;
- 删除旧字段。
反例是一次发布同时:
删除旧列
部署只认识新列的代码
如果新代码回滚,而旧代码已经无法访问被删除的列,回滚就不再是应用层可完成的操作。
迁移前应验证:
- 备份是否存在;
- 备份是否可以恢复;
- 迁移在副本数据库上耗时多久;
- 是否锁表;
- 是否可重复执行;
- 失败后数据库处于什么状态;
- 应用旧版本能否继续运行。
5. 灰度发布
灰度发布不是简单地“把 10% 请求转到新版本”,而是一个带观测和停止条件的状态机:
旧版本 100%
|
| 部署新版本,健康检查通过
v
旧版本 90% / 新版本 10%
|
| 错误率、延迟、业务指标正常
v
旧版本 50% / 新版本 50%
|
| 持续验证
v
新版本 100%
灰度路由可以按:
- 随机比例;
- 用户 ID;
- 租户;
- 地域;
- 请求头;
- 功能开关。
按用户 ID 灰度通常能保持同一用户体验稳定,但需要注意哈希分布和用户迁移。按随机请求灰度可能导致同一用户在两个版本间来回切换。
灰度期间至少观察:
- 5xx 错误率;
- P95/P99 延迟;
- 超时率;
- 数据库慢查询;
- 队列积压;
- 业务成功率;
- 新旧版本结果差异;
- 资源使用率。
6. 回滚
回滚有三种不同含义:
- 流量回滚:把请求切回旧版本;
- 代码回滚:部署旧构建产物;
- 数据回滚:恢复或逆向修改数据。
前两者通常较快,数据回滚风险最大。已经提交的业务数据不能简单依靠“撤销部署”恢复。
可靠的发布流程应保存:
构建产物版本
配置版本
数据库迁移版本
依赖锁定信息
灰度指标
回滚命令
验证命令
每个发布动作都要有验证步骤。例如:
# 1. 检查服务健康
curl -fsS http://127.0.0.1:8000/health
# 2. 检查关键业务
curl -fsS http://127.0.0.1:8000/api/version
# 3. 检查日志中是否出现启动错误
journalctl -u example.service --since "5 minutes ago"
命令成功不代表业务正确。curl 返回 HTTP 200,只能说明网络请求和健康接口成功,不能证明数据库写入、消息投递或模型输出正确。
十一、推荐的阶段性学习路线
阶段一:语言核心
目标是能够解释每个表达式和对象变化。
学习内容:
语法与求值
名称、对象、身份、类型和值
可变性与复制
作用域、闭包、参数绑定
函数、类和协议
异常与资源管理
模块与导入
迭代器与生成器
练习重点不是刷题,而是写出对象关系和状态变化。例如:
- 实现浅复制与深复制对比;
- 编写带闭包状态的计数器;
- 实现上下文管理器;
- 追踪循环导入;
- 用生成器处理大文件。
阶段二:工程基础
学习:
虚拟环境
依赖管理
日志
配置
测试
类型检查
代码质量检查
调试和性能分析
交付一个命令行项目,要求:
- 可以从干净环境安装;
- 有明确入口;
- 有单元测试;
- 非法输入有错误信息;
- 日志不泄露密钥;
- 依赖和 Python 版本有记录。
阶段三:并发
按以下顺序:
线程与共享状态
锁、条件变量、队列
进程与进程间通信
Executor 与 Future
asyncio 事件循环
协程、Task、取消和超时
结构化并发
阻塞代码的隔离
练习项目可以是一个批量 URL 检查器:
- 同步版本;
- 线程池版本;
- asyncio 版本;
- 进程池版本;
- 比较吞吐、错误传播、超时和取消行为。
不要只比较运行时间。要记录:
- 并发数;
- 每种 I/O 的等待时间;
- 失败数量;
- 超时数量;
- 内存;
- 是否有任务泄漏。
阶段四:Web 与数据
实现一个带数据库的服务:
用户注册
-> 身份验证
-> 资源创建
-> 分页查询
-> 事务更新
-> 审计日志
要求明确:
- 请求校验;
- 错误状态码;
- 数据库事务;
- 幂等键;
- 查询索引;
- 超时;
- 健康检查;
- 日志和请求 ID。
阶段五:AI 应用
先做可评估的小项目:
文本分类
-> 向量检索
-> RAG 问答
-> 结构化输出
-> 工具调用
-> Web 服务化
每个阶段都保留固定测试集,记录模型版本、Prompt 版本、检索参数和输出评估结果。否则模型升级后无法判断是模型变化、数据变化还是业务代码变化导致质量波动。
阶段六:生产交付
最后把一个服务完整交付:
构建
-> 测试
-> 迁移
-> 部署
-> 健康检查
-> 灰度
-> 指标观察
-> 全量
-> 回滚演练
必须实际演练:
- 数据库迁移失败;
- 新版本启动失败;
- 外部服务超时;
- 消息重复投递;
- worker 被杀死;
- 连接池耗尽;
- 配置缺失;
- 模型加载失败;
- 灰度指标恶化。
十二、用一个纵向项目串起全部知识
可以构建一个“异步数据与 AI 服务”作为贯穿项目:
HTTP API
|
+-- 用户与权限
+-- 任务提交
+-- 异步任务队列
+-- 数据库
+-- 文件对象存储
+-- 文本切分与向量化
+-- 检索与模型推理
+-- 结果查询
+-- 指标、日志、审计
它能覆盖:
- Python 名称和对象;
- 类型标注和异常;
- 模块边界;
- asyncio 调度;
- 线程池隔离阻塞库;
- 进程或解释器池执行 CPU 任务;
- HTTP 请求生命周期;
- 数据库事务;
- AI 推理;
- 进程模型与容量;
- 配置、迁移、灰度和回滚。
最终验收标准不是“接口能返回结果”,而是能够回答:
- 请求进入哪个进程和哪个协程?
- 等待数据库时事件循环是否被阻塞?
- 任务取消时资源是否释放?
- 数据库事务在哪一步提交?
- 同一任务重复执行是否安全?
- 多个 worker 的缓存是否一致?
- 模型加载了几份?
- 数据库连接池是否会耗尽?
- 新旧代码能否共存于迁移期间?
- 发布失败后能否只回滚流量而不破坏数据?
能够回答这些问题,才说明已经从“会写 Python”进入“理解 Python 系统”。
Python 3.14 的学习重点仍然不是记住更多 API,而是建立跨层因果链:名称如何指向对象,协程如何被事件循环调度,Web 请求如何穿过进程和连接池,数据如何在事务中改变,模型如何在服务中占用资源,以及一次发布如何安全地改变整个系统。
完整学习目录
一、语言与运行模型
- Python 3.14 工具链:安装、解释器、REPL、脚本与版本管理
- Python 执行模型:源码、AST、字节码、解释器帧与调用栈
- Python 词法与语法基础:缩进、Token、表达式、语句和注释
- Python 名称、对象与绑定:变量不是盒子,赋值不是复制
- Python 数值类型:int、float、complex、bool 与运算边界
- Python 字符串、bytes 与 Unicode:编码、解码和文本边界
- Python list、tuple 与 range:存储、切片、复杂度和选择
- Python dict 与 set:哈希、顺序、冲突、复杂度和键约束
- Python 控制流:if、for、while、break、else 与作用域
- Python 结构化模式匹配:match、case、守卫、绑定与陷阱
- Python 函数与参数:位置、关键字、默认值、解包和返回
- Python 作用域与闭包:LEGB、nonlocal、global 和迟绑定
- Python 装饰器:函数包装、参数化、类装饰器与元数据
- Python 可迭代对象与迭代器:协议、惰性、耗尽和组合
- Python 生成器:yield、send、throw、close 与委托
- Python 推导式:列表、集合、字典、生成器与作用域边界
- Python 异常体系:传播、链、分组、清理和错误契约
- Python 上下文管理器:with、协议、ExitStack 与资源安全
- Python 模块与包:命名空间、init、入口和项目边界
- Python 导入系统:搜索路径、缓存、Finder、Loader 与循环依赖
二、对象与类型系统
- Python 对象模型:type、object、属性查找、身份与生命周期
- Python 类与继承:实例、类属性、组合、覆盖和边界
- Python MRO 与 super:C3 线性化、多继承和协作调用
- Python 描述符与 property:属性访问、绑定方法和 ORM 基础
- Python ABC 与 Protocol:名义子类型、结构化类型和接口设计
- Python dataclass 与 Enum:数据模型、不可变性、比较和序列化
- Python 特殊方法:容器、运算符、调用、表示与上下文协议
- Python 元类:类创建流程、prepare、注册和使用边界
- Python 反射与自省:inspect、签名、Frame、AST 和安全边界
- Python 类型标注基础:Union、Literal、TypedDict、Narrowing 与边界
- Python 泛型类型:TypeVar、ParamSpec、TypeVarTuple 与 Protocol
- Python 复制与序列化:浅拷贝、深拷贝、pickle 和不可信输入
- Python 内存与垃圾回收:引用计数、循环 GC、分代与诊断
- Python weakref 与 slots:生命周期观察、对象布局和内存取舍
- Python GIL 与自由线程构建:互斥边界、扩展兼容和并行选择
三、标准库与系统编程
- Python 文件系统与 pathlib:路径、遍历、元数据、原子替换和竞态
- Python I/O 模型:文本、二进制、缓冲、编码和流式处理
- Python 结构化数据:JSON、CSV、TOML、Schema 与精度边界
- Python 日期时间:datetime、时区、DST、时间戳和序列化
- Python 正则表达式:匹配模型、分组、回溯、性能和 Unicode
- Python collections:deque、Counter、defaultdict、ChainMap 与队列
- Python itertools:惰性组合、无限序列、分组和内存边界
- Python functools:缓存、偏函数、归约、分派和装饰器工具
- Python Decimal 与 Fraction:精度、舍入、上下文和金融计算
- Python 随机数与 secrets:伪随机、采样、种子和密码学边界
- Python 子进程与信号:参数传递、管道、超时、退出和回收
- Python 日志工程:Logger、Handler、结构化字段、上下文和轮转
- Python 配置管理:环境变量、文件、校验、Secret 与多环境
- Python CLI 工程:argparse、子命令、退出码、管道和可测试性
四、并发、异步与网络
- Python asyncio 完整基础:事件循环、协程、Future 与调度
- Python 异步任务:create_task、TaskGroup、异常组和结构化并发
- Python asyncio 网络流:TCP、Reader、Writer、TLS 与背压
- Python asyncio 取消与同步:Timeout、Lock、Queue 和清理
- Python 线程:生命周期、锁、条件变量、竞态和死锁诊断
- Python 多进程:启动方式、IPC、共享内存、Pool 与回收
- Python concurrent.futures:线程池、进程池、Future 和取消
- Python 队列与背压:queue、asyncio.Queue、容量和关闭协议
- Python Socket 编程:TCP、UDP、地址、半关闭、超时和协议帧
- Python HTTP 客户端:连接池、超时、重试、流式和 TLS
- Python 哈希、HMAC 与 TLS:完整性、密码存储、证书和误区
五、工程质量与包管理
- Python pyproject.toml:构建系统、项目元数据、入口和工具配置
- Python 虚拟环境:venv、解释器隔离、路径和常见污染
- Python 依赖解析与锁定:版本约束、Lock、哈希和供应链
- Python 包构建与发布:wheel、sdist、索引、签名和版本
- Python 代码质量:Ruff、格式化、Lint、导入排序和规则治理
- Python pytest 完整基础:Fixture、参数化、标记、插件和隔离
- Python unittest 与 Mock:替身边界、Patch、异步和脆弱测试
- Python 属性测试:Hypothesis、生成策略、缩减和状态机测试
- Python 静态类型工程:mypy、Pyright、Stub、渐进迁移和 CI
- Python 调试:pdb、断点、异常现场、日志和远程诊断
- Python 性能剖析:timeit、cProfile、tracemalloc、采样和证据链
- Python 基准测试:预热、噪声、统计、pyperf 和回归判断
- Python 安全工程:输入、注入、反序列化、依赖、Secret 和沙箱
- Python 应用架构:模块边界、依赖方向、领域层和可替换适配器
六、Web、数据与服务开发
- FastAPI 完整基础:路由、依赖注入、校验、异步和生命周期
- Pydantic v2:模型、校验器、序列化、Settings 和性能边界
- Flask 完整基础:应用工厂、Context、Blueprint、扩展和部署
- Django 完整基础:项目、请求链、模型、模板、Admin 和安全
- Django REST Framework:Serializer、ViewSet、权限、分页和限流
- Python WSGI 与 ASGI:调用协议、并发模型、中间件和部署选择
- Python Web API 工程:契约、错误、分页、幂等、限流和版本
- SQLAlchemy 2.0:Engine、Session、映射、查询、事务和 N+1
- Alembic 数据库迁移:Revision、自动生成、分支、上线和回滚
- Python 异步数据库访问:连接池、事务、取消、并发和一致性
- Python 与 Redis:连接池、Pipeline、事务、缓存和分布式锁
- Celery 任务队列:Broker、Worker、ACK、重试、定时和幂等
- Python 消息系统:RabbitMQ、Kafka、消费语义、重试和死信
- Python gRPC:Protobuf、流式调用、Deadline、拦截器和错误模型
- Python 实时通信:WebSocket、SSE、心跳、广播和背压
- Python Web 认证与授权:Session、JWT、OAuth 2.0、RBAC 和 CSRF
- Python 文件上传与后台处理:流式、校验、对象存储和任务状态
- Python 网络采集:Requests、HTTPX、BeautifulSoup、限速和合规
七、数据、AI 与桌面开发
- NumPy 完整基础:ndarray、形状、广播、索引、向量化和内存布局
- pandas 完整基础:Series、DataFrame、索引、缺失值、分组和连接
- Python 数据可视化:Matplotlib、Seaborn、坐标、布局和可解释图表
- scikit-learn 工程基础:Pipeline、预处理、训练、评测和持久化
- Python PyTorch 工程:Tensor、Autograd、Dataset、训练和检查点
- Jupyter 工程化:Kernel、Notebook 状态、复现、参数化和安全
- Python OpenAI SDK:Responses、流式、结构化输出、工具和重试
- Python Agent 框架选型:原生循环、Agents SDK、LangGraph 和 PydanticAI
- Python Tkinter GUI:事件循环、布局、控件、线程和打包
- Python PySide 与 Qt:信号槽、Model/View、线程、资源和发布
- Python 自动化脚本:文件、命令、网络、重试、幂等和审计
- Python 定时任务:时间语义、调度器、重复执行、锁和补偿
八、性能、交付与生产治理
- Python 国际化:gettext、Locale、日期数字、消息目录和回退
- Python 可观测性:日志、指标、Trace、Context 和故障定位
- Python 集成测试:Testcontainers、数据库、消息队列和稳定隔离
- Python 原生扩展与性能:C API、Cython、PyO3、ABI 和边界
- Python 实验可复现:随机种子、环境、数据、制品和运行记录
- Python 容器与 CI:镜像、依赖缓存、测试、制品和供应链
- Python 生产交付:进程模型、容量、配置、迁移、灰度和回滚
系列导航与关联阅读
- 下一篇:Python 3.14 工具链:安装、解释器、REPL、脚本与版本管理
- 延伸:Python 名称、对象与绑定:变量不是盒子,赋值不是复制
- 延伸:Python asyncio 完整基础:事件循环、协程、Future 与调度
- 延伸:Python 生产交付:进程模型、容量、配置、迁移、灰度和回滚
官方资料
本文依据 Python 官方文档、相关 PEP 与生态项目官方文档重新梳理;正文、示例与工程清单由 WR BLOG 编写。

评论
0 条讨论