1.FastAPI是什么?
FastAPI 是一个基于Python 3.6+和Starlette (Web 部分)+Pydantic (数据验证部分)构建的现代、快速的 Web 框架 ,主要用于开发 API(尤其是 RESTful API)。它的名字就体现了两个特点:Fast (性能高效)和API(专注接口开发)。
不熟悉这一块的我发现几个不了解的名词:Web框架,Starlette,Pydantic(包括数据验证),API(包括 RESTful API)。下面,我们分别简短介绍一下这几个名词:
1.1Web框架
1.1.1什么是Web框架?
Web 框架(Web Framework) = 一组工具和库,用来帮助开发者更高效地构建 Web 应用或 API。
Web 应用的本质是:
客户端(浏览器、App、其他服务) → 发送 HTTP 请求 → 服务器 → 返回 HTTP 响应。
而 Web 框架就是帮你处理这些请求与响应的底层细节,让你不用从零开始写"HTTP 协议解析器"。
如果没有框架,你得自己写类似这样的东西:
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("127.0.0.1", 8080))
server_socket.listen(5)
while True:
client_socket, addr = server_socket.accept()
request = client_socket.recv(1024).decode("utf-8")
response = "HTTP/1.1 200 OK\\n\\nHello World"
client_socket.send(response.encode("utf-8"))
client_socket.close()
这就是一个"手写服务器"。它能跑,但:
- 你要自己解析 HTTP 协议(GET/POST/Headers/Body)
- 你要自己写路由分发逻辑
- 你要自己处理 JSON、Cookies、Session、模板、数据库交互
太原始了。
1.1.2为什么需要 Web 框架?
Web 框架出现就是为了解决 重复劳动 和 复杂性 问题。
它们帮我们解决了这些事:
- 路由分发 :不同 URL → 不同函数,比如
/users
→ 用户接口。 - 请求解析:自动解析 HTTP 请求(Query、Body、Headers、Cookies)。
- 响应构建:自动构造 HTTP 响应,支持 JSON/HTML/File 等。
- 中间件机制:可以在请求/响应前后插入逻辑(日志、认证、安全检查)。
- 开发效率提升:很多常见功能直接内置(模板渲染、ORM、验证等)。
- 生态和规范:框架通常有庞大社区,提供插件、扩展和最佳实践。
换句话说:
即,Web 框架把底层重复的、易错的工作抽象出来,让开发者专注于业务逻辑。
1.2 Starlette
1.2.1什么是Starlette?
Starlette 是一个基于 Python 的 轻量级 ASGI Web 框架/工具包。
- ASGI (Asynchronous Server Gateway Interface)是 WSGI 的升级版,专门为 异步支持(async/await)而设计。
- Starlette 提供了一套构建 Web 应用的核心能力:路由、请求/响应处理、中间件、WebSocket、后台任务等。
它本身是一个 极简的框架,功能够用,但不会捆绑一大堆东西。
可以理解为:Starlette 是 FastAPI 的地基。
1.2.2为什么需要Starlette?
在 FastAPI 出现之前,Python 的主流 Web 框架(Flask/Django)是基于 WSGI 的,而 WSGI 是 同步模型,在高并发或需要 WebSocket 的场景下效率不高。
于是 Starlette 出现了:
- 支持 ASGI → 异步请求处理,性能更好
- 提供一套 现代化的 Web 框架工具集 → 不用从零写
1.2.3什么是路由?
在 Web 应用中,路由就是把 URL(路径)和对应的处理函数关联起来的规则。
简单来说:
客户端访问哪个 URL,服务器就调用哪个函数来处理请求。
比如:
- 用户访问
http://example.com/home
→ 返回首页内容 - 用户访问
http://example.com/users/123
→ 返回 ID=123 的用户数据
1.2.4 为什么需要路由?
想象一下,如果没有路由,你得自己写代码解析用户请求的 URL,然后用 if/else
去判断调用哪个函数,非常麻烦:
if path == "/home":
homepage()
elif path.startswith("/users/"):
get_user()
else:
not_found()
而有了 路由系统,你只需要声明规则,框架会自动帮你匹配。
1.3 Pydantic
1.3.1 什么是 Pydantic?
Pydantic 是一个 Python 的数据验证和数据解析库。
它的核心作用是:
根据 Python 类型注解(type hints)来验证数据,并自动转换成正确的类型。
也就是说,你只需要用 Python 的类型系统(str
, int
, list
, dict
, ...)定义数据结构,Pydantic 就能帮你:
- 解析输入数据(比如 JSON)
- 验证数据合法性(比如 age 必须是整数,email 必须是邮箱格式)
- 自动转换类型 (比如
"123"
自动转成int 123
)
1.3.2 为什么需要 Pydantic?
在 Web 应用中,我们经常需要处理用户输入:
- 表单提交
- JSON 请求体
- URL 参数
这些输入往往"不靠谱",可能类型不对、字段缺失、格式错误。
如果不用 Pydantic,我们得手动写验证逻辑,比如:
def create_user(data: dict):
if "name" not in data or not isinstance(data["name"], str):
raise ValueError("Invalid name")
if "age" not in data or not isinstance(data["age"], int):
raise ValueError("Invalid age")
return {"name": data["name"], "age": data["age"]}
很啰嗦,而且容易漏掉。
有了 Pydantic,这些验证可以通过声明式模型自动完成。
1.3.3 Pydantic 怎么用?
Pydantic 提供了一个基类 BaseModel,你可以继承它来定义数据模型:
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
使用:
user = User(name="Alice", age="20")
print(user)
# name='Alice' age=20 (字符串 "20" 自动转成 int)
如果数据不合法:
user = User(name="Alice", age="abc")
# 会抛出 ValidationError: value is not a valid integer
1.3.4 Pydantic 的功能
- 数据验证(Validation)
- 类型验证(int/str/float/list/dict...)
- 格式验证(邮箱、URL、UUID...)
- 范围验证(长度、最大值、最小值)
- 数据解析(Parsing)
- 自动转换类型,比如
"123"
→123
- 支持复杂嵌套结构
- 自动转换类型,比如
- 数据序列化(Serialization)
- 可以把模型转成 JSON / dict,方便返回 API 响应
- 文档生成(Schema)
- Pydantic 可以自动生成 JSON Schema
- FastAPI 利用这个特性自动生成 API 文档
1.3.5 FastAPI 为什么用 Pydantic?
FastAPI 的一个核心设计是:
所有请求体 / 查询参数 / 响应体 都用 Pydantic 模型来定义。
例子:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post("/users/")
def create_user(user: User):
return {"msg": f"Hello {user.name}, you are {user.age} years old"}
流程:
- 客户端发送请求
POST /users {"name": "Tom", "age": "30"}
- FastAPI 用
User
模型解析 JSON- 检查字段是否完整
- 验证
age
是否是整数 - 自动把
"30"
转换成30
- 如果验证失败,返回 422 错误 + 错误提示
- 如果验证成功,传递一个
User
实例给create_user
函数
1.4 API
API(Application Programming Interface,应用程序编程接口)是一套规则和协议,定义了不同软件应用程序之间如何相互通信和交互。简单来说,API就像是软件之间的"桥梁"或"翻译器"。
1.4.1 API的核心概念
API规定了:
- 可以发出什么样的请求
- 如何发出这些请求
- 使用什么数据格式
- 遵循什么约定
就像餐厅的菜单一样,API告诉你可以"点什么菜"(可用功能)以及"怎么点菜"(调用方式)。
1.4.2 RESTful API详解
REST(Representational State Transfer,表现状态转移)是一种架构风格,RESTful API是遵循REST原则设计的API。
REST的核心原则:
1. 无状态性(Stateless)
- 每个请求都包含处理该请求所需的所有信息
- 服务器不保存客户端的状态信息
2. 统一接口(Uniform Interface)
- 使用标准的HTTP方法(GET、POST、PUT、DELETE等)
- 资源通过URL来标识
- 使用标准的HTTP状态码
3. 客户端-服务器架构
- 明确分离用户界面和数据存储
- 提高了可移植性和可扩展性
4. 可缓存性(Cacheable)
- 响应数据可以被标记为可缓存或不可缓存
RESTful API的常见操作:
GET /api/users # 获取所有用户
GET /api/users/123 # 获取ID为123的用户
POST /api/users # 创建新用户
PUT /api/users/123 # 更新ID为123的用户
DELETE /api/users/123 # 删除ID为123的用户
1.4.3 API的实际应用场景
- 移动应用:手机App通过API获取服务器数据
- 网站集成:网站嵌入地图、支付、社交媒体功能
- 微服务架构:不同服务模块之间的通信
- 第三方服务:如天气API、翻译API、支付API等
1.4.4 优势
- 模块化:不同系统可以独立开发和维护
- 重用性:一个API可以被多个应用程序使用
- 灵活性:可以更换底层实现而不影响使用者
- 标准化:RESTful API提供了统一的交互方式
API本质上让不同的软件能够"对话",而RESTful API则提供了一套标准化的"对话规则",使得这种交流更加高效和可预测。
2.为什么需要FastAPI(FastAPI是解决什么问题?)
在 FastAPI 出现之前,Python 主流的 Web 框架主要是:
- Flask :轻量、灵活,但缺少内置的验证、文档生成功能,开发大型项目需要写很多样板代码。
- Django :功能齐全,但更适合做"全栈"应用(数据库 + 模板渲染 + 管理后台),对单纯的 API 项目来说显得笨重。
- Tornado:异步框架,适合高并发,但开发体验不如 Flask 友好。
这些框架在 开发现代 API 时往往存在几个痛点:
(1)数据验证繁琐:需要手动解析和验证请求体参数(比如 JSON),容易遗漏。
(2)文档缺失:接口文档通常需要单独维护,很难和代码保持同步。
(3)性能问题:传统的同步框架在高并发下效率较低。
(4)异步支持不友好:Python 的 async/await 出现后,很多旧框架集成不够顺畅。
3.FastAPI是如何解决这些问题的?
FastAPI 的设计理念就是:
高性能 + 开发效率 + 自动化文档 + 类型安全
具体来说:
(1) 类型提示驱动
FastAPI 深度利用了 Python 的 类型注解(type hints):
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post("/users/")
def create_user(user: User):
return {"msg": f"Hello {user.name}, you are {user.age} years old"}
- 这里
User
模型由 Pydantic 自动做验证,保证请求体符合要求。 - 如果客户端发错类型(比如
age
传了"abc"
),FastAPI 会自动返回 422 错误。
(2) 自动生成文档
启动服务后,可以直接访问:
-
http://127.0.0.1:8000/docs
→ Swagger UI -
http://127.0.0.1:8000/redoc
→ ReDoc文档完全根据代码自动生成,不需要额外维护。
(3) 异步性能
FastAPI 基于 Starlette ,天然支持 async/await ,性能接近 Node.js 和 Go 的 Web 框架。
(4) 开发效率
- 自动完成 数据解析 → 验证 → 错误提示
- 自动生成 交互式文档
- 代码量少,清晰可维护
4.FastAPI的优缺点
和其他方案相比,FastAPI的优缺点如下:
4.1 FastAPI的优点
- 高性能:基于 Starlette,性能在 Python 框架中名列前茅。
- 开发效率高:几乎"零配置",类型注解直接变成参数验证规则。
- 文档自动化:自带 Swagger + ReDoc,不需要额外写 API 文档。
- 类型安全:利用 Python typing 和 Pydantic,减少运行时错误。
- 异步支持:适合高并发场景(爬虫接口、实时数据服务)。
- 社区发展快:近几年在后端开发者中非常流行。
4.2 FastAPI的缺点
- 生态较新:相比 Django/Flask,周边库、教程和案例还没那么丰富。
- 学习曲线:需要熟悉 Python 类型注解和 Pydantic,否则会觉得陌生。
- 全栈能力不足:不像 Django 自带 ORM、Admin 等,做 Web 全栈项目需要自己拼接其他库(比如 SQLAlchemy、Jinja2)。
- 极端高性能不如 Go/Node:虽然在 Python 里算快,但和 Go、Rust 等语言比还是有差距。
5.一些概念
5.1 FastAPI的handler
在 FastAPI 里,所谓的 handler 一般就是指 路由处理函数(endpoint function)。
当客户端(比如浏览器或其他服务)发起一个 HTTP 请求时,FastAPI 会根据请求路径和方法,把请求分发给对应的 handler 来处理,然后返回响应。
举个例子
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
async def say_hello():
return {"message": "Hello, world!"}
这里的 say_hello
就是一个 handler ,它对应一个 GET /hello 的请求。
- 当有人访问
http://127.0.0.1:8000/hello
时,FastAPI 会调用say_hello()
这个 handler。 - handler 可以是
async def
(异步函数),也可以是def
(同步函数)。 - handler 的返回值会被 FastAPI 自动转换成 HTTP Response。
handler 的几个特点
- 路由绑定 :通过装饰器
@app.get()
,@app.post()
,@app.put()
等绑定到具体的路径和 HTTP 方法(路径就是上面例子中的"/hello")。 - 参数处理:FastAPI 会自动把 URL 参数、查询参数、请求体解析出来,直接传给 handler 的函数参数。
- 异步执行 :推荐用
async def
,这样 FastAPI 可以利用 asyncio 提升并发性能。
5.2事件循环event loop
理解事件循环event loop这个概念是理解FastAPI这类Python异步编程的核心。
5.2.1 事件循环是什么?
事件循环 是一个不停"转圈"的调度器,它的职责是:
- 管理所有的 异步任务(coroutines / futures / tasks)
- 监控 I/O 状态(比如网络、文件、socket)
- 决定什么时候让哪个任务继续执行,什么时候挂起等待(调度器的职责)
换句话说:
事件循环是异步程序的大脑,负责协调多个任务在同一个线程里并发执行。
5.2.2 为什么需要事件循环?
传统的同步代码是这样的:
def task1():
time.sleep(3) # 阻塞3秒
print("task1 done")
def task2():
time.sleep(2) # 阻塞2秒
print("task2 done")
task1()
task2()
执行时间 = 3 + 2 = 5秒 。因为 sleep
阻塞了整个线程。
异步方式则不同:
import asyncio
async def task1():
await asyncio.sleep(3)
print("task1 done")
async def task2():
await asyncio.sleep(2)
print("task2 done")
async def main():
await asyncio.gather(task1(), task2())
asyncio.run(main())
这里 asyncio.sleep
并不会阻塞线程,而是把"等待 3 秒钟"交给 事件循环 管理。
事件循环会在这 3 秒里继续跑别的任务,所以最终耗时只有 3秒(而不是 5 秒)。
5.2.3 事件循环是怎么工作的?
可以类比成电影院的放映员:
- 观众(任务)坐在位置上等待。
- 放映员(事件循环)会巡视:
- "你准备好了吗?"
- 如果某个观众(任务)需要等待(比如 I/O 还没完成),就先跳过。
- 如果准备好了,就让他执行一小段(
await
之前的代码)。
- 不停循环,直到所有任务都完成。
所以它本质上是:单线程里轮流调度多个任务,让它们看起来像"并发"。
5.2.4 Python 里的事件循环
在 Python asyncio
里:
asyncio.run(main())
会创建并启动一个事件循环。await
会把控制权交回给事件循环。- 事件循环会不断运行,直到所有任务完成。
也可以手动操作:
import asyncio
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
5.2.5 常见误区
-
事件循环通常 只能有一个(每个线程一个),否则会乱套。
-
它不是并行(parallel),只是并发(concurrent)------真正的并行要用多线程/多进程。
-
如果在事件循环里再用
asyncio.run()
(相当于再开一个 loop),就会报你之前那个错:RuntimeError: asyncio.run() cannot be called from a running event loop
6.几个Demo
需要安装的模块:
pip install fastapi uvicorn "python-jose[cryptography]" passlib[bcrypt]
6.1 Demo1
学习目标:理解 FastAPI 的基本结构、路由、请求方法
功能点
- 创建一个 FastAPI 应用
- 定义一个
GET
路由返回 JSON - 定义一个
POST
路由接收参数并返回
示例代码
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
# 定义请求体数据模型
class Item(BaseModel):
name: str
price: float
@app.get("/")
def read_root():
return {"message": "Hello, FastAPI!"}
@app.get("/hello/{name}")
def read_item(name: str):
return {"greeting": f"Hello, {name}"}
@app.post("/items/")
def create_item(item: Item):
return {"item": item}
💡 学会使用:
- 路由 (
@app.get
,@app.post
) - 路径参数 (
/hello/{name}
) - 请求体 + Pydantic 模型
启动流程
-
把代码保存为
demo1.py
-
运行:
uvicorn demo1:app --reload 注意: 如果报了"[WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。",则可以考虑换一个port口打开,如下面的指令: uvicorn demo1:app --reload --port 8001
demo1
是文件名(不带.py
)app
是 FastAPI 实例对象-reload
代表热更新,修改代码不用重启
测试方法
-
打开浏览器访问:
http://127.0.0.1:8000/
→ 返回{"message": "Hello, FastAPI!"}
http://127.0.0.1:8000/hello/Jerry
→ 返回{"greeting": "Hello, Jerry"}
-
使用 Swagger UI:
- 访问
http://127.0.0.1:8000/docs
,你可以直接点按钮测试 API
- 访问
-
curl
测试 POST:curl -X POST <http://127.0.0.1:8000/items/> \\ -H "Content-Type: application/json" \\ -d '{"name":"apple","price":3.5}'
6.2 Demo2
学习目标:掌握 CRUD、依赖注入、状态存储
功能点
- 用 内存列表 模拟数据库
- 实现 CRUD(增删改查)
- 使用
Query
、Path
、Body
参数 - 引入依赖函数(例如检查 item 是否存在)
示例代码
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
app = FastAPI()
class Todo(BaseModel):
id: int
title: str
completed: bool = False
todos = []
def get_todo(todo_id: int):
for todo in todos:
if todo.id == todo_id:
return todo
raise HTTPException(status_code=404, detail="Todo not found")
@app.post("/todos/", response_model=Todo)
def create_todo(todo: Todo):
todos.append(todo)
return todo
@app.get("/todos/", response_model=list[Todo])
def list_todos():
return todos
@app.get("/todos/{todo_id}", response_model=Todo)
def read_todo(todo: Todo = Depends(get_todo)):
return todo
@app.put("/todos/{todo_id}", response_model=Todo)
def update_todo(todo_id: int, updated: Todo):
for i, todo in enumerate(todos):
if todo.id == todo_id:
todos[i] = updated
return updated
raise HTTPException(status_code=404, detail="Todo not found")
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
for i, todo in enumerate(todos):
if todo.id == todo_id:
todos.pop(i)
return {"message": "Deleted successfully"}
raise HTTPException(status_code=404, detail="Todo not found")
💡 学会使用:
- CRUD API 设计
- 依赖注入 (
Depends
) - 错误处理(
HTTPException
) - 返回数据模型(
response_model
)
启动流程
-
保存为
demo2.py
-
启动:
uvicorn demo2:app --reload
测试方法
-
创建任务:
curl -X POST <http://127.0.0.1:8000/todos/> \\ -H "Content-Type: application/json" \\ -d '{"id":1,"title":"Learn FastAPI","completed":false}'
-
查看所有任务:
curl <http://127.0.0.1:8000/todos/>
-
查看单个任务:
curl <http://127.0.0.1:8000/todos/1>
-
更新任务:
curl -X PUT <http://127.0.0.1:8000/todos/1> \\ -H "Content-Type: application/json" \\ -d '{"id":1,"title":"Learn FastAPI deeply","completed":true}'
-
删除任务:
curl -X DELETE <http://127.0.0.1:8000/todos/1>
👉 同时你也可以去 http://127.0.0.1:8000/docs
直接点点点操作,方便很多。
6.3 Demo3
学习目标:掌握 认证、依赖注入、响应模型、JWT
功能点
- 用户注册 & 登录
- 密码哈希存储
- 使用 JWT 进行登录认证
- 保护需要权限的接口
示例代码
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from passlib.context import CryptContext
import jwt
from datetime import datetime, timedelta
app = FastAPI()
# 密码加密工具
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
SECRET_KEY = "mysecret"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
# 模拟数据库
fake_users = {}
class User(BaseModel):
username: str
password: str
def authenticate_user(username: str, password: str):
user = fake_users.get(username)
if not user or not pwd_context.verify(password, user["hashed_password"]):
return None
return username
def create_access_token(username: str):
expire = datetime.utcnow() + timedelta(minutes=30)
return jwt.encode({"sub": username, "exp": expire}, SECRET_KEY, algorithm=ALGORITHM)
@app.post("/register")
def register(user: User):
if user.username in fake_users:
raise HTTPException(status_code=400, detail="User already exists")
fake_users[user.username] = {"hashed_password": pwd_context.hash(user.password)}
return {"msg": "User registered successfully"}
@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
username = authenticate_user(form_data.username, form_data.password)
if not username:
raise HTTPException(status_code=401, detail="Invalid credentials")
token = create_access_token(username)
return {"access_token": token, "token_type": "bearer"}
@app.get("/protected")
def protected(token: str = Depends(oauth2_scheme)):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
return {"msg": f"Hello {username}, you accessed a protected route!"}
💡 学会使用:
- 用户注册 / 登录
OAuth2PasswordBearer
+Depends
- 密码加密 (
passlib
) - JWT 认证 & 保护路由
启动流程
-
保存为
demo3.py
-
启动:
uvicorn demo3:app --reload
测试方法
-
注册新用户
curl -X POST <http://127.0.0.1:8000/register> \\ -H "Content-Type: application/json" \\ -d '{"username":"jerry","password":"123456"}'
-
登录获取 Token
curl -X POST <http://127.0.0.1:8000/login> \\ -H "Content-Type: application/x-www-form-urlencoded" \\ -d "username=jerry&password=123456"
你会得到:
{ "access_token": "xxxxx.yyyyy.zzzzz", "token_type": "bearer" }
-
访问保护接口
curl -X GET <http://127.0.0.1:8000/protected> \\ -H "Authorization: Bearer xxxxx.yyyyy.zzzzz"
成功的话返回:
{"msg": "Hello jerry, you accessed a protected route!"}
👉 也可以直接去 http://127.0.0.1:8000/docs
:
- 点 Authorize 按钮
- 输入
"Bearer <你的token>"
- 测试
/protected
接口,验证权限控制