D6 学习笔记:第一个 FastAPI——从「看不见后端」到「能跑的服务」
系列:海口三港 AI 全栈实战 · 从参赛大屏到 AI 平台
仓库:https://github.com/2003Tim/haikou-ai-port
前言:两个里程碑
D6 我做了两件让我兴奋的事:
- 用 50 行代码实现了一个 MiniFastAPI,把 FastAPI 内部机制看透了
- 真正跑起来一个 uvicorn 服务,浏览器打开
/docs自动看到 5 个 API 文档
之前参赛时,我对"后端"是完全黑盒的——后端同学说"调这个接口就行",我照做。D6 我亲手让一个 HTTP 请求从浏览器走到 Python 函数,又走回浏览器,这感觉完全不同。
一、uvicorn 是什么?(一句话)
uvicorn 是 ASGI 服务器——它的工作是接收 HTTP 请求 → 转给 FastAPI 应用 → 把结果返回。
餐厅类比
浏览器 ──HTTP 请求──▶ uvicorn(前台) ───▶ FastAPI(厨房)
浏览器 ◀──HTTP 响应── uvicorn(上菜) ◀──── FastAPI(做菜)
uvicorn 本身不懂业务,只懂 HTTP 协议。真正的逻辑在 FastAPI 里。
为什么是 uvicorn?
| 服务器 | 适合 | 速度 |
|---|---|---|
app.run()(Flask 自带) |
Flask(同步) | 🐢 |
gunicorn |
Flask、Django(WSGI) | 🐇 |
uvicorn |
FastAPI(ASGI) | 🚀 |
FastAPI 是异步框架,必须用 ASGI 服务器,uvicorn 是事实标准,底层用 uvloop(C 写的异步循环)极快。
二、一个 HTTP 请求的完整旅程
浏览器输入 http://127.0.0.1:8000/ports,发生了什么?
浏览器 ──TCP 握手──▶ uvicorn
发送:GET /ports HTTP/1.1
Host: 127.0.0.1:8000
│
▼
uvicorn 解析 HTTP 请求
│
▼
uvicorn 调用 FastAPI 应用
│
▼
FastAPI 查路由表:
GET /ports → list_ports()
│
▼
调用 list_ports() 返回 [{"id":1,...}]
│
▼
Pydantic + JSON 序列化:
[{"id":1,"name":"秀英港"},...]
│
▼
uvicorn 写回 socket
│
▼
浏览器 ──收到 HTTP/1.1 200 OK──▶ 渲染 JSON
8 个步骤,但 FastAPI 帮你做了大部分。
三、为什么访问 URL 就自动调用函数?
答案: 装饰器 + 路由表。
@app.get("/ports")
def list_ports():
return [...]
等价于:
list_ports = app.get("/ports")(list_ports)
app.get("/ports") 返回装饰器,装饰器把 (method, path, func) 存到路由表:
self.routes = [
("GET", "/", read_root),
("GET", "/ports", list_ports),
("GET", "/hello/{name}", say_hello),
...
]
请求到来时,FastAPI 在路由表里查找匹配项,找到就调用对应函数。
路径参数提取:
/hello/Tim→ 匹配/hello/{name},提取name="Tim"/ports/2→ 匹配/ports/{port_id},提取port_id="2"
四、我自己写了一个 MiniFastAPI 看内部
50 行代码,核心就这 4 个步骤:
class MiniFastAPI:
def get(self, path):
def decorator(func):
self.routes.append(("GET", path, func))
return func
return decorator
def handle_request(self, method, path):
for route_method, route_path, handler in self.routes:
if route_method == method and path == route_path:
result = handler()
return json.dumps(result, ensure_ascii=False)
return json.dumps({"error": "Not Found"})
跑完演示,我真的看到了:
- 路由表在
@app.get(...)执行时就建立 - 请求到来时遍历路由表查找
- 找到后调用函数 + JSON 序列化
- 找不到返回 404
这就是 FastAPI 的核心。真实的 FastAPI 多了 Pydantic 校验、Swagger UI、异步、依赖注入等,但基础就是这个 50 行版本。
五、装饰器的3 个常见坑(D4 学的,D6 实战)
坑1:wrapper 不调用 func
def bad_decorator(func):
def wrapper(*args, **kwargs):
print("before")
# ❌ 没调用 func!
print("after")
return wrapper
@bad_decorator
def slow_function():
time.sleep(1)
return "done"
slow_function() # 原函数完全没执行!
坑2:wrapper 不 return result
def timer(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f"耗时")
# ❌ 没 return result
return wrapper
@timer
def get_name():
return "Tim"
print(get_name()) # 输出 None!
坑3:wrapper 名字可以随便改
def timer(func):
def inner(*args, **kwargs): # ← 改名为 inner
...
return inner
改名字不影响功能,但约定用 wrapper,代码更易读。
装饰器正确模板(必须 3 件事)
def my_decorator(func):
def wrapper(*args, **kwargs): # ① 定义新函数
# 前置逻辑
result = func(*args, **kwargs) # ② 调用原函数
# 后置逻辑
return result # ③ return 原函数的返回值
return wrapper # ④ return wrapper
六、JSON 序列化是干什么的?
Python 对象(dict / list)内存里的,浏览器看不懂。HTTP body 是字符串,所以要序列化:
import json
port = {"id": 1, "name": "秀英港"}
port_str = json.dumps(port, ensure_ascii=False)
# '{"id": 1, "name": "秀英港"}' ← 浏览器能解析这个
为什么是 JSON 而不是别的?
- 几乎所有语言都支持
- 人类可读
- 体积小(对比 XML)
七、Swagger UI 是 FastAPI 的杀手锏
跑 uvicorn app.main:app --reload,浏览器打开 /docs:
- 自动列出所有 API
- 每个 API 可以直接"Try it out"测试
- 路径参数、查询参数自动生成输入框
- 响应示例自动展示
对比 Postman:Postman 需要手动建请求,Swagger UI 框架帮你生成。这是 FastAPI 比 Flask 体验好太多的地方。
九、D6 跑通后的 5 个 API
GET / → 欢迎信息
GET /hello/{name} → 个性化问候(路径参数)
GET /ports → 港口列表
GET /ports/{id} → 单个港口(路径参数 + 查询参数 include_stats)
GET /health → 健康检查
到 D6 结束时,我有了:
- 一个能
uvicorn起来的 Python Web 服务 - 一个真正能用的 API(虽然数据是硬编码)
- 自动生成的 Swagger UI
这是 v1.0 的起点。
下一步:D7 学什么?
D7 我会用 Pydantic 做数据校验,把硬编码的港口数据结构化——这是接入数据库前的最后一步。
到 D7 结束时,我会:
- 港口数据有
PortPydantic 模型(替代 dict) - API 自动校验请求参数(类型、必填、范围)
- 错误返回标准化(422 而不是 500)
参考资料
- FastAPI 官方文档:https://fastapi.tiangolo.com/zh/
- uvicorn 文档:https://www.uvicorn.org/
- ASGI 规范:https://asgi.readthedocs.io/
- JSON 教程:https://www.json.org/json-zh.html

浙公网安备 33010602011771号