大家好,今天我们来聊一个在 Python 和 FastAPI 开发中既基础又强大的工具:装饰器 (Decorator)。
很多使用 FastAPI 的朋友对装饰器肯定不陌生。我们每天都在用它来定义路由:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def read_root():
return {"Hello": "World"}
这里的 @app.get("/") 就是一个装饰器。它把一个普通的 Python 函数 read_root 变成了一个处理 HTTP GET 请求的 API 端点。它很神奇,也很好用。但除了框架提供给我们的这些,我们能不能创建自己的装饰器来解决特定问题,让代码变得更优雅、更易于维护呢?
答案是肯定的。今天,我们就从一个常见的场景出发,一步步构建一个实用的自定义装饰器。
场景:接口权限控制
假设我们正在开发一个后台管理系统。系统里有不同角色的用户:普通用户、管理员、超级管理员。某些敏感接口,比如“删除用户”,我们只希望“超级管理员”才能调用。
最直接的想法可能是在接口函数内部做判断:
from fastapi import Depends, HTTPException, status
# 伪代码:假设我们有一个函数可以从 Token 中获取当前用户信息
async def get_current_user(token: str):
# ... 实现获取用户的逻辑 ...
# 返回一个包含角色信息 User 对象
return current_user
@app.delete("/users/{user_id}")
async def delete_user(user_id: int, current_user: User = Depends(get_current_user)):
# 权限检查逻辑
if "super_admin" not in current_user.roles:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="对不起,你没有权限执行此操作",
)
# 真正的业务逻辑
# ... 删除用户的代码 ...
return {"message": "用户删除成功"}
这个实现方式完全没问题。但设想一下,如果系统里有十几个甚至几十个接口都需要“超级管理员”权限,我们是不是要在每个接口里都重复写一遍这段 if 判断逻辑?这显然违反了 DRY (Don’t Repeat Yourself) 原则,不仅啰嗦,而且未来如果权限逻辑需要变更(比如增加一个“审计员”角色也能访问),我们就得去修改所有相关的接口,非常容易出错。
这时候,就该轮到自定义装饰器出场了。
动手:创建一个权限检查装饰器
我们的目标是创建一个 @require_role('super_admin') 装饰器,把权限检查的逻辑从业务代码中抽离出来。
首先,我们得理解装饰器在 Python 中到底是什么。简单来说,装饰器就是一个接收函数作为参数,并返回一个新函数的函数。它允许我们在不修改原函数代码的情况下,为函数增加额外的功能。
让我们来构建这个权限装饰器。在 FastAPI 中,因为大量使用依赖注入(Dependency Injection),我们的装饰器需要巧妙地与之结合。
# 我们可以把这个文件放在例如 src/core/security.py
from functools import wraps
from fastapi import Depends, HTTPException, status
from .dependency import get_current_user # 假设从这里导入
from ..models.admin import User # 假设用户模型在这里
def require_role(required_role: str):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
# 关键点:如何在装饰器内部拿到依赖注入的结果?
# FastAPI 的路由函数参数是通过 kwargs 传递给装饰器的
# 但依赖注入的参数在调用时才解析,这里直接拿不到
# 所以我们需要找到 current_user
# 一个聪明的做法是,让被装饰的函数签名中必须包含 current_user
# FastAPI 会在调用 wrapper 前解析依赖
# 但更通用的做法是,在装饰器内部也使用 Depends
# 不过为了简化,我们先假设依赖已经被解析并存在于 kwargs 中
# 让我们换一种更 FastAPI-style 的思路
# 创建一个返回依赖的依赖项
# 最终,最优雅的方式是创建一个返回函数的函数
pass # 先跳过实现,我们看下面更优雅的方式
return wrapper
return decorator
上面的思路有点复杂。在 FastAPI 的世界里,我们可以利用 Depends 来让事情变得更简单。我们可以创建一个“依赖项类”,它本身可以像函数一样被调用。
一个更清晰、更符合 FastAPI 风格的实现如下:
# 我们可以把这个文件放在例如 src/core/permissions.py
from functools import wraps
from fastapi import Depends, HTTPException, status
from ..models.admin import User # 假设用户模型
from .dependency import get_current_user # 假设获取当前用户的依赖
def require_permission(permission: str):
"""
创建一个依赖项,该依赖项会检查用户是否具有所需权限。
"""
def permission_checker(current_user: User = Depends(get_current_user)):
if not hasattr(current_user, 'permissions') or permission not in current_user.permissions:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"权限不足,需要 '{permission}' 权限",
)
return current_user
return Depends(permission_checker)
使用这种方式,我们可以这样改造我们的接口:
from fastapi import APIRouter
from ..core.permissions import require_permission
router = APIRouter()
@router.delete(
"/users/{user_id}",
dependencies=[require_permission("user:delete")] # 使用 dependencies 参数
)
async def delete_user(user_id: int):
# 业务逻辑
# ...
return {"message": "用户删除成功"}
看,现在 delete_user 函数变得多么干净!它只关心自己的核心业务逻辑。所有关于权限检查的复杂性都被封装在了 require_permission 中。如果未来权限逻辑需要修改,我们只需要更新这一个地方。
为什么这很重要?
这种做法不仅仅是代码量的减少,它体现了一种更优秀的设计思想——关注点分离 (Separation of Concerns)。
- API 路由函数:只关心接收请求、调用业务服务、返回响应。
- 权限装饰器:只关心验证用户身份和权限。
- 业务服务:只关心核心的业务逻辑(比如数据库操作)。
每一部分都只做一件事,并把它做好。
推广时间:我的 FastAPI 模板项目
在实际项目中,类似这样的代码组织和设计模式是提升项目质量和可维护性的关键。这也是我创建 FastAPI-Template 的初衷。
我的这个模板项目已经为大家搭建好了一个健壮的、可扩展的应用骨架。它不仅包含了:
- 清晰的项目结构划分 (
api,core,services,models等)。 - 开箱即用的用户认证和 JWT 支持。
- 数据库集成和模型管理。
- 统一的日志和异常处理。
更重要的是,它鼓励像我们今天讨论的这种最佳实践。你可以很容易地在 src/core/ 目录下添加像 permissions.py 这样的模块来扩展系统功能,而不会让代码变得混乱。它为你处理了所有繁琐的“样板代码”,让你能从第一天起就专注于实现有价值的业务功能。
总结
装饰器是 Python 提供的一个强大工具。在 FastAPI 中,除了使用框架内置的装饰器,学会根据业务场景创建自己的装饰器(或类似的依赖项),是每一位进阶开发者的必经之路。它能极大地提升代码的可读性、可重用性和可维护性。
如果你正在寻找一个能帮你实践这些优秀思想的 FastAPI 项目起点,不妨看看我的 FastAPI-Template 。
希望今天的分享对你有所启发!
664

被折叠的 条评论
为什么被折叠?



