FastAPI 入门:从路由到依赖注入
本页记录已经学习的 FastAPI 基础:路由、请求参数、请求体与 Pydantic、异步、
Depends,以及理解它们所需的 Python 装饰器、闭包与协程概念。目标是建立一条完整主线:请求如何找到函数、数据如何进入函数、函数如何在等待 I/O 时协作,以及请求所需的信息如何被准备好。
1. FastAPI 接口:请求如何到达 Python 函数
from fastapi import FastAPI
app = FastAPI()
@app.get("/hello")
def hello():
return {"message": "Hello"}FastAPI()创建 Web 应用对象;@app.get("/hello")声明:当收到GET /hello时,交给下面的hello()函数处理;- 函数的返回值会被 FastAPI 转成 HTTP 响应。
可以把路由表理解为:
HTTP Method + Path → Python 函数
GET + /hello → hello()路径必须写在 app.get() 中。只有 @app.get 而没有 "/hello",框架就不知道哪一个 URL 应对应这个函数。
2. Python 装饰器:为什么 @app.get() 能注册路由
2.1 函数也是对象
Python 中函数可以赋给变量、作为参数传入另一个函数,也可以作为返回值返回:
def greet():
return "hi"
f = greet
print(f()) # hi这里 f 和 greet 都引用同一个函数对象;f() 并不是把函数内容复制了一遍。
2.2 @decorator 的展开形式
@decorator
def hello():
return "hello"等价于:
def hello():
return "hello"
hello = decorator(hello)也就是说,装饰器先接收原函数,再把它的返回结果重新绑定给同名变量 hello。
一个“给函数加日志”的例子:
def log_call(func):
def wrapper():
print("调用前")
result = func()
print("调用后")
return result
return wrapper
@log_call
def hello():
return "hello"现在调用 hello(),实际调用的是 wrapper();但 wrapper() 内部仍通过 func() 调用原来的 hello 函数。
hello 变量 → wrapper 函数 → 原 hello 函数原函数没有消失,只是不再由 hello 这个名字直接引用。
2.3 闭包:内部函数为什么还能使用外层变量
def outer():
x = 10
def inner():
return x
return inner
f = outer()
print(f()) # 10inner() 被返回后,仍持有它所使用的外层变量 x。这种“函数连同其所需外部变量一起保留下来”的现象叫作闭包。
outer() 执行结束,不代表 x 必然被回收:只要 f 还引用着 inner,而 inner 的闭包还引用 x,这条引用链就仍然存在。
f → inner → 闭包中的 x闭包捕获的是变量绑定,而不是简单复制一个当时的值。因此,如果外层和内部函数共享并修改同一个变量,内部函数可以观察到更新后的值;在内层函数中给该变量重新赋值时,需要使用 nonlocal 表示要修改外层变量。
2.4 FastAPI 的装饰器和普通包装装饰器不同在哪里
普通装饰器常常返回 wrapper,在每次调用函数时加行为。FastAPI 的路由装饰器则主要在应用加载时登记信息:
@app.get("/hello")
def hello():
return {"message": "Hello"}可近似理解为:
decorator = app.get("/hello")
hello = decorator(hello)app.get("/hello") 先得到一个装饰器;该装饰器拿到 hello 后,把“GET /hello 对应 hello”记录到路由表中。它的核心目的不是在每次请求前执行一段包装逻辑。
Python 装饰器与 Spring AOP 都可以表现为“在原逻辑周围增加行为”,但 FastAPI 这里更接近注册路由元数据,不能简单等同于 @Before、@After 或 @Around。
3. 请求参数:URL 与函数参数如何对应
3.1 路径参数(Path Parameter)
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}请求 GET /users/100 时,user_id 得到整数 100。它和 Spring MVC 的 @PathVariable 类似。
/users/100
↓
user_id = 1003.2 类型声明、解析和校验
在普通 Python 中,x: int 主要是类型注解,本身不会自动把字符串转换为整数,也不会强制校验。
FastAPI 会利用这个类型信息:
HTTP 中的 "123"
↓
看到 user_id: int
↓
解析并校验
↓
函数收到 Python int 123如果请求 /users/abc,它不符合 int,FastAPI 会在进入业务函数前返回参数错误。
3.3 查询参数(Query Parameter)
@app.get("/users/{user_id}")
def get_user(user_id: int, detail: bool):
return {"user_id": user_id, "detail": detail}请求:
GET /users/100?detail=true得到:
user_id == 100
detail is True判断规则可以先记为:函数参数若出现在路径模板 /xxx/{参数名} 中,就是路径参数;不在路径中且是简单类型时,通常是查询参数。
?page=2&size=10 是 HTTP 的 query string,由 Web 框架解析;它和 Python 的字典解包 func(**data) 是不同概念。
3.4 必填、默认值和可选值
page: int # 必填
page: int = 1 # 不传时使用 1
keyword: str | None = None # 可传字符串或 None;不传时为 Nonestr | None表示类型允许为None;= None表示调用方未提供时的默认值是None。
二者含义不同。类型是否允许空值,与参数是否有默认值,需要分别判断。
4. 请求体与 Pydantic:JSON 如何成为对象
创建用户时,数据通常放在 HTTP 请求体中:
{
"name": "Tom",
"age": 20
}from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class User(BaseModel):
name: str
age: int
@app.post("/users")
def create_user(user: User):
return userBaseModel 不是 Python 原生类,而是 Pydantic 提供的模型基类。FastAPI 会把请求 JSON 交给 Pydantic 解析和校验,再把得到的 User 对象传给函数;这与 Spring 中的 @RequestBody UserDTO 很相似。
HTTP JSON → Python 数据 → Pydantic 解析与校验 → User 对象 → 业务函数字段约束可以写在模型上:
class User(BaseModel):
age: int = Field(ge=18)这类约束负责格式、类型和范围等输入校验,类似 Java Bean Validation。至于“该用户是否已经存在”“库存是否足够”这类业务规则,应放在 Service 或业务代码中,而不是只靠字段校验完成。
5. 异步、协程与 await
5.1 def 和 async def
def hello():
return "hello"
async def async_hello():
return "hello"调用普通函数 hello() 会直接执行并得到结果。调用 async_hello() 则先得到一个协程对象(coroutine object),需要由事件循环调度执行。
协程可以理解为“可以在等待时暂停、以后再继续执行的函数”。它尤其适合网络、数据库等 I/O 等待场景。
5.2 await:等待时让出执行机会
await async_operation()await 用于等待另一个可等待对象完成。若它在等待 I/O,当前协程会暂停并把执行机会交还给事件循环;事件循环可以去推进其他已准备好的协程。
任务 A 执行
↓
await I/O,A 暂停
↓
事件循环推进任务 B
↓
A 的 I/O 完成
↓
继续 A这不是让单个 CPU 同时执行两段 Python 计算,而是避免在 I/O 等待期间空等。
5.3 同步阻塞与异步等待
import asyncio
import time
time.sleep(3) # 当前线程被阻塞 3 秒
await asyncio.sleep(3) # 当前协程等待,事件循环可运行其他任务关键点是:写成 async def,不代表其中所有代码自动异步。若在 async def 中直接调用 time.sleep()、同步数据库驱动或其他耗时阻塞函数,仍可能堵住事件循环。
5.4 FastAPI 为什么支持两种写法
@app.get("/sync")
def sync_handler():
...
@app.get("/async")
async def async_handler():
...- 使用异步 I/O 库时,通常使用
async def并配合await; - 调用同步阻塞库时,使用普通
def更合适,FastAPI 可以将同步的路径处理函数放到线程池中执行; - 不要为了“看起来异步”而在
async def内直接运行阻塞式代码。
6. Depends:按一次请求准备所需信息
from fastapi import Depends
@app.get("/orders")
def get_orders(user = Depends(get_current_user)):
return {"user": user}可以先按下面的顺序理解:
执行 get_orders 前
↓
执行并解决 get_current_user
↓
把结果作为 user 传入
↓
执行 get_ordersDepends 常用于当前用户、Token、数据库 Session 和权限检查等“处理一次请求之前需要先准备”的东西。
6.1 与 Spring DI 的区别
Spring 的依赖注入常表示对象之间的依赖,例如 Controller 依赖 UserService,类似 @Autowired。
FastAPI 的 Depends 更适合从一次请求出发理解:这个处理函数为了完成当前请求,需要什么数据或前置处理? 两者都有“由框架提供依赖”的相似性,但不能简单视为同一个概念。
6.2 登录鉴权中的依赖链
user = Depends(get_current_user)它可以表示这样的过程:
Request
↓
提取 Token
↓
验证 Token
↓
获得当前用户
↓
注入接口函数
↓
业务代码依赖本身也可以继续依赖别的依赖,因此可以形成一条链:
接口 → 权限检查 → 当前用户 → Token → Request7. 当前掌握范围
FastAPI
├── Python 前置概念:函数对象、装饰器、闭包
├── 路由:@app.get、@app.post、路由表
├── 请求参数:Path、Query、必填/默认/可选
├── 请求体与数据模型:Pydantic、BaseModel、字段校验
├── 异步:async def、协程、await、事件循环、阻塞与异步 I/O
└── 依赖注入:Depends后续尚未学习的异常处理、响应模型、路由模块化、CRUD、数据库与登录认证不在本页范围内。