关于 FastAPI
FastAPI 是 2018 年新出的 Python Web 框架,专门写 API 接口(不是渲染网页的)。它有 3 个杀手锏:
💬 什么是「接口(API)」
前几章的 Flask/Django,是把数据塞进 HTML 网页再整页发给浏览器。而「接口」只发纯数据(一段 JSON 文本),完全不管页面长什么样。打个比方:网页是后厨做好、摆好盘端上桌的一道菜;接口是后厨直接把食材打包递出窗口——谁来取(网页、手机 App、小程序)、拿去怎么摆盘,由对方自己决定。所谓前后端分离,就是后端只管做接口、只送数据。
- 速度快——基于 Starlette + Pydantic + 异步,性能接近 NodeJS / Go。
- 类型提示自动校验——你声明
q: int,传来字符串就自动 422 报错,不用手写校验。 - 自动生成 API 文档——访问
/docs直接出现 Swagger UI,可以在浏览器里调接口。
💡 应用场景
Flask/Django 适合做完整网站(含页面)。FastAPI 适合做纯接口——给前端 Vue/React、移动 App、小程序提供数据。第15章我们就用 FastAPI 给微信小程序做后台。
1 安装
安装 FastAPI 和 uvicorn
💬 为什么要装两个包
fastapi 是框架本身(写代码用);uvicorn 是 ASGI 服务器(跑代码用)。Flask/Django 自带开发服务器,FastAPI 必须配一个外部服务器,uvicorn 是官方推荐的。
# 一行装齐:fastapi 是框架,uvicorn[standard] 是 ASGI 服务器
$ pip3 install fastapi "uvicorn[standard]" -i https://pypi.tuna.tsinghua.edu.cn/simple
# 验证
$ python3 -c "import fastapi; print(fastapi.__version__)"
0.111.0
⚠️ 引号别忘
"uvicorn[standard]" 必须带引号。中括号在很多 shell 里是特殊字符,不加引号会报错。
★ 预备
①
就像在门上挂门牌——
规矩:装饰器必须紧贴函数定义,中间不能空行、不能插别的语句。
②
特别注意:
③
像点奶茶:甜度默认「正常糖」,你不说就给正常糖(有默认值=可选);但「哪种茶底」必须自己选(没默认值=必填)。
④
JSON 是前后端之间传数据的「通用文本格式」,长得几乎和 Python 字典一模一样。可以这样记:字典是你随手写的便签,JSON 是把它誊写成谁都能读懂的标准格式。
⑤
像填空模板:
代码里反复出现的几个写法
下面 main.py 里,有几种 Python 写法会反复出现。它们不是 FastAPI 发明的,是 Python 自带的语法。把每一种先读成一句「人话」,后面看代码就不会卡。
① @app.get("/") 装饰器——给函数「挂门牌」
💬 人话翻译
@ 开头、紧贴在函数上一行的那句,叫「装饰器」。整句读作:「下面这个函数,专门负责处理 GET / 这个网址的请求。」就像在门上挂门牌——
@app.get("/items") 等于在函数门口挂一块「GET /items 请进」的牌子;请求按门牌找到该进哪个函数。规矩:装饰器必须紧贴函数定义,中间不能空行、不能插别的语句。
② item_id: int 类型提示——写明「应该是什么类型」
💬 人话翻译
参数名后面加 : 类型,是在告诉 FastAPI「这个参数应该是什么类型」。item_id: int 读作「item_id 是一个整数」。特别注意:
: 不是赋值,= 才是赋值。写了类型,FastAPI 就替你自动做两件事——检查 + 转换:网址里传来的本是文字 "42",它帮你转成整数 42;遇到转不了的(比如 abc)就自动报错。
③ skip: int = 0 默认值——决定参数「必填还是可选」
💬 人话翻译
在类型后面再加 = 某个值,就给参数定了一个「默认值」。客户端没传它 → 自动用默认值,于是这个参数变可选;要是没写 = → 就是必填,不传直接报错。像点奶茶:甜度默认「正常糖」,你不说就给正常糖(有默认值=可选);但「哪种茶底」必须自己选(没默认值=必填)。
④ return {...} 返回字典——自动变成 JSON
💬 人话翻译
函数里 return 一个大括号字典 {"键": 值},FastAPI 会自动把它打包成 JSON 文本发给浏览器,你不用手动转。JSON 是前后端之间传数据的「通用文本格式」,长得几乎和 Python 字典一模一样。可以这样记:字典是你随手写的便签,JSON 是把它誊写成谁都能读懂的标准格式。
⑤ f"用户#{user_id}" f-字符串——往文字里「填空」
💬 人话翻译
字符串前面加一个小写 f,里面 {} 中的变量就会被换成它的实际值。若 user_id 是 42,f"用户#{user_id}" 就得到字符串 "用户#42"。像填空模板:
f"亲爱的{名字}",把名字填进去再用。
2 第一个程序
app = FastAPI():造出「应用」对象
@app.get("/"):门牌——它下面紧挨着的
{item_id} + item_id: int:网址里大括号是「占位符」,访问
HelloWorld + 自动 API 文档
新建文件 /home/fastapi/main.py,敲入下面这段。每一行都配了注释,对照上面的「几个写法」一起读:
# ============ FastAPI 最小程序 ============
from fastapi import FastAPI
# 造一个「应用」对象,整个程序都挂在它身上
#(和 Flask 的 app = Flask(__name__) 一个意思)
app = FastAPI()
# 装饰器=挂门牌:GET 访问 / 时,就执行下面这个函数
@app.get("/")
def read_root():
# return 一个字典,FastAPI 自动转成 JSON 发出去
return {"message": "Hello, FastAPI!"}
# 网址里 {item_id} 是占位符;item_id: int 是类型提示
@app.get("/items/{item_id}")
def read_item(item_id: int):
# 访问 /items/42:42 自动装进 item_id,并转成整数
# 访问 /items/abc:转不成整数,自动返回 422,函数都不执行
return {"item_id": item_id, "price": 9.99}
💬 这段代码,从上往下读一遍
第 1 行 from fastapi import FastAPI:把 FastAPI 这个工具取过来备用。app = FastAPI():造出「应用」对象
app,后面所有接口都挂在它身上。@app.get("/"):门牌——它下面紧挨着的
read_root 负责处理 GET /;函数 return 字典,自动变 JSON。{item_id} + item_id: int:网址里大括号是「占位符」,访问
/items/42 时 42 被装进参数 item_id;又因为标了 : int,FastAPI 自动把文字转成整数,转不了就报 422。
启动方式不一样
FastAPI 不能直接 python main.py。要用 uvicorn 把它跑起来:
$ cd /home/fastapi
# main 是文件名(main.py),app 是文件里的实例对象名
# --reload 改代码自动重启,--host 0.0.0.0 让外网能访问
$ uvicorn main:app --reload --host 0.0.0.0 --port 8000
INFO: Uvicorn running on http://0.0.0.0:8000
💬
冒号两边分开看:main:app 到底指什么main 是文件名(main.py 去掉 .py),app 是你在文件里写的那个 app = FastAPI() 变量名。合起来=「去 main.py 里找名叫 app 的应用,把它跑起来」。哪天文件名或变量名改了,这里要跟着改。
测试三个 URL
GET
http://IP:8000/
→ {"message": "Hello, FastAPI!"}
GET
http://IP:8000/items/42
→ {"item_id": 42, "price": 9.99}
GET
http://IP:8000/docs
★ 自动生成的交互式文档
🎉 重头戏:自动 API 文档
访问 /docs,看到的就是 Swagger UI——所有接口列表 + 在线试用
点开任一接口 → "Try it out" → 填参数 → "Execute",不用 Postman 也能测
💡 还有 /redoc
/redoc 是另一种风格的文档(更适合阅读,不能在线试),/docs 适合开发调试。两个同时存在,按需用。
3 Path & Query
比如上面这个 5 个元素的列表,
路径参数 vs 查询参数
💬 一图看清两种参数
http://api.com/users/42?page=2&size=10 ↑ ↑ Path 路径参数 Query 查询参数 (路径的一部分) (问号后面 key=value)
规则:函数参数到底算哪种
FastAPI 靠一个简单规则区分:看参数名有没有在网址的 {} 里出现过。
| 你写的参数 | FastAPI 当成 |
|---|---|
名字在 {} 里出现过 | Path 参数(网址路径的一部分) |
| 名字没在 URL 里出现 | Query 参数(问号后面的 key=value) |
带默认值(如 = 0) | 该参数可选 |
| 不带默认值 | 该参数必填 |
追加到 main.py:
# ===== Path 参数:名字出现在网址的 {} 里 =====
@app.get("/users/{user_id}")
def get_user(user_id: int):
# user_id 在 {} 里 → Path 参数;: int → 自动校验+转换
return {"user_id": user_id, "name": f"用户#{user_id}"}
# ===== Query 参数:名字没出现在网址里 =====
@app.get("/articles/")
def list_articles(skip: int = 0, limit: int = 10):
# skip、limit 没在 {} 里 → Query 参数
# 都有默认值 → 可选;不传就是 0 和 10
# 先用一个写死的小列表当假数据
articles = ["文章A", "文章B", "文章C", "文章D", "文章E"]
# 列表切片:跳过 skip 个,再往后取 limit 个
return articles[skip : skip + limit]
# ===== Path + Query 混着用 =====
@app.get("/users/{user_id}/articles")
def user_articles(user_id: int, q: str = None):
# user_id 在 {} 里 → Path;q 不在 → Query
# q: str = None → 可选的字符串,默认没有
result = {"user_id": user_id}
if q:
result["search"] = q # 传了 q 才加进结果
return result
💬
方括号里写 articles[skip : skip + limit] 是「列表切片」[起 : 止],能从列表里截出一段,规则是含起点、不含终点。比如上面这个 5 个元素的列表,
articles[1:3] 取出第 1、2 个(即 文章B、文章C)。这里写成 skip : skip + limit,意思就是「跳过 skip 个,再往后拿 limit 个」——正好是翻页的逻辑。
测试一下
GET/users/42→ Path 参数
GET/users/abc→ 自动 422 错误
GET/articles/?skip=1&limit=2→ 取出 文章B、文章C
GET/users/42/articles?q=python→ Path + Query
💡 自动校验是 FastAPI 最香的特性
你只是写了 user_id: int,FastAPI 就替你做了:参数提取、类型转换、错误处理、生成文档。这一点 Flask/Django 都做不到——它们都得手写校验。
📋 本节小结
这一章的命令清单
pip3 install fastapi "uvicorn[standard]"— 安装uvicorn main:app --reload --host 0.0.0.0— 启动http://IP:8000/docs— 在线调试 API
Path 参数 vs Query 参数,一句话分清
- 名字出现在网址
{}里 → Path 参数(如/users/{user_id}的 user_id)。 - 名字没出现在网址里 → Query 参数(问号后面的
key=value,如?skip=1)。 - 有默认值 → 可选;没默认值 → 必填。
FastAPI 跟另两个框架的核心差异
- 没有模板——FastAPI 不渲染 HTML,只返回 JSON。
- 没有 ORM——要操作数据库自己装 SQLAlchemy / Tortoise / 直接用 PyMySQL。
- 类型提示就是文档——你写
q: int,FastAPI 替你做:校验、转换、错误响应、文档生成。