D4 学习笔记:装饰器、生成器、async——FastAPI 三大底层技能
系列:海口三港 AI 全栈实战 · 从参赛大屏到 AI 平台
仓库:https://github.com/2003Tim/haikou-ai-port
前言:D4 学什么?
D3 我学了 Python 基础 —— 类型注解、Optional、@dataclass。这些是 FastAPI 的"语法基础"。
但 D3 我看 FastAPI 文档时还是一脸懵,直到 D4 学了这三个东西 —— 装饰器、生成器、async —— 才恍然大悟:
@app.get("/")不再神秘,因为理解了装饰器- LLM 流式输出不再抽象,因为理解了生成器
- FastAPI 为什么快不再神奇,因为理解了 async
今天这篇博客记下这三个概念,以及它们如何对应到 FastAPI 的设计。
一、装饰器:函数包装函数
概念
装饰器是接收函数、返回新函数的可调用对象。@decorator 是语法糖:
# 下面这两段代码完全等价
# 写法 1:@ 语法糖
@timer
def slow_function():
...
# 写法 2:手动调用装饰器
def slow_function():
...
slow_function = timer(slow_function)
我写的练习:计时装饰器
def timer(func):
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
print(f"[{func.__name__}] 耗时 {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function():
time.sleep(0.3)
为什么 FastAPI 必须用装饰器
FastAPI 路由就是装饰器:
@app.get("/ports") # ← 装饰器!
def get_ports():
return [...]
@app.get("/ports") 做了三件事:
- 把
get_ports函数注册到 FastAPI 的路由表 - 把路径
/ports和 GET 方法关联到这个函数 - 读 type hints 自动生成 Swagger 文档
💡 装饰器的真正威力是横向切面:鉴权(
@require_login)、日志(@log_calls)、缓存(@cached)都可以用一个装饰器加上去,不用改业务代码。
坑:装饰器会"偷走"函数名
我的 timer 装饰器打印的是 [wrapper] 而不是 [hello],因为:
def timer(func):
def wrapper(*args, **kwargs): # ← 新函数叫 wrapper,不是 hello
print(f"[{func.__name__}]") # func 是 wrapper,不是原函数!
修复用 functools.wraps:
from functools import wraps
def timer(func):
@wraps(func) # ← 把原函数的 __name__、__doc__ 复制过来
def wrapper(*args, **kwargs):
...
这个细节我第一次写装饰器就踩了,记下来提醒自己。
二、生成器:用 yield 产出多个值
概念
普通函数 return 一次就结束,生成器函数用 yield 可以产出多个值,每次调用 next() 才执行到下一个 yield:
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a # ← 不是 return,是 yield
a, b = b, a + b
fib = fibonacci(10)
print(next(fib)) # 0
print(next(fib)) # 1
print(next(fib)) # 1
我写的练习:模拟 LLM 流式输出
def mock_llm_stream(prompt):
response = f"AI 助手回答关于 {prompt} 的问题:..."
for word in response.split():
yield word + " "
time.sleep(0.05) # 模拟一个 token 一个 token 生成
跑出来的效果是一个字一个字蹦出来,和真实 LLM 的"打字机效果"一模一样。
为什么 LLM 必须用流式
LLM 不是一次生成一篇文章,是一个 token 一个 token 生成(每个几毫秒)。如果 API 等所有 token 生成完再返回:
- 用户要等 5-10 秒才看到第一个字
- 体验极差,以为"卡住了"
流式输出让用户边生成边显示,ChatGPT 的"打字机效果"就是这个。
FastAPI 里用 StreamingResponse 把生成器包装成 HTTP 流式响应:
from fastapi.responses import StreamingResponse
@app.post("/chat")
def chat(prompt: str):
def generate():
for token in llm.stream(prompt): # 调真实 LLM API
yield token
return StreamingResponse(generate(), media_type="text/plain")
💡 v3.0 阶段做 RAG 智能问答时,前端会看到"AI 助手正在输入..."的效果 —— 就是这个 yield 的功劳。
三、async/await:为什么 FastAPI 这么快
震撼的对比
我的练习里:
| 方式 | 10 个 I/O 请求 | 耗时 |
|---|---|---|
| 顺序 await(同步思维) | 一个一个等 | 1.007s |
| asyncio.gather(并发) | 一起等 | 0.103s |
| 加速比 | — | 9.8x |
10 倍加速!这还是纯本地 asyncio.sleep,真实网络请求差距更大。
为什么
Web 服务器大部分时间都在等 I/O —— 数据库查询、调用其他 API、读文件。
- 同步代码:等 I/O 时线程闲着(浪费资源)
- 异步代码:等 I/O 时让出控制权,服务器去处理其他请求
FastAPI 用 async 是因为:一个进程能处理上千并发,同步 Flask 几百就卡。
FastAPI 里的 async
@app.get("/ports")
async def get_ports():
# await 数据库(让出控制权,服务器去处理其他请求)
ports = await db.fetch_all("SELECT * FROM ports")
return ports
async def 告诉 FastAPI:这个函数会 await,不要阻塞线程。
关键认知
- FastAPI 同时支持
def(同步)和async def(异步)路由 - 异步路由里不能调用阻塞的同步代码,否则就白异步了
- 异步代码里只能 await 其他异步函数
⚠️ 实际项目中有个经典坑:在 async 函数里调
requests.get()(同步),会阻塞整个事件循环。这种情况要用httpx.AsyncClient替代。
我踩过的坑
-
装饰器链的返回值忘了 print
我写了@timer @repeat(times=2) def hello(),但调用时只写了hello("海口三港")没接收返回值。结果 list 被丢了,只看到 timer 打印的那一行。修复:加result = hello(...)+print(result)。 -
timer 显示
[wrapper]而不是[hello]
装饰器"偷走"了原函数的__name__。修复用@functools.wraps(func)保留元数据。 -
async 函数里调用同步代码会阻塞事件循环
这条我现在还没踩,但提前记下:在async def里不要用requests/time.sleep/同步数据库驱动,要用对应的 async 版本(httpx、asyncpg、aiomysql 等)。
完整代码
git clone https://github.com/2003Tim/haikou-ai-port.git
cd haikou-ai-port/backend
uv venv
.\.venv\Scripts\Activate.ps1
python learn_d4.py
下一步:D5 学什么?
D5 我会装 Postman(或 Apifox) + 学 HTTP 基础 —— GET/POST、状态码、JSON、请求头。这些是后端工程师和 AI 工程师的"通用语言",面试必问。
D6-D7 就要迎来 第一个 FastAPI Hello World —— 所有前面学的语法都会在这里汇合。
参考资料
- 《流畅的 Python》第 9 章(装饰器)、第 17 章(async)
- FastAPI 官方文档:https://fastapi.tiangolo.com/
- Real Python - Decorators:https://realpython.com/primer-on-python-decorators/
- Real Python - Async:https://realpython.com/async-io-python/

浙公网安备 33010602011771号