理解 Python 装饰器:@ 的强大功能

在 Python 中,@ 符号与**装饰器(Decorator)**紧密相关。装饰器是一种强大且灵活的工具,允许在不修改函数、方法或类源代码的情况下,扩展或修改其行为。本文将深入探讨装饰器的工作原理、执行时机以及实际应用,结合通用 Python 编程和 FastAPI 框架的示例。

什么是装饰器?

装饰器是一个高阶函数(或可调用对象),通过包装另一个函数或类来添加功能。Python 的 @ 符号是装饰器的语法糖,用于简洁地应用装饰器。例如:

def my_decorator(func):
    def wrapper():
        print("函数调用前")
        func()
        print("函数调用后")
    return wrapper

@my_decorator
def say_hello():
    print("你好!")

say_hello()

输出:

函数调用前
你好!
函数调用后

在这里,@my_decorator 将 say_hello 函数包装在一个 wrapper 函数中,添加了调用前后打印日志的功能。@ 语法等价于:

say_hello = my_decorator(say_hello)

装饰器何时执行?

装饰器的执行分为两个阶段:

  1. 代码解析阶段(模块加载时):
    • 当 Python 加载模块时,遇到 @decorator,会立即执行装饰器函数(例如 my_decorator),将目标函数(例如 say_hello)作为参数传递,并返回一个新的函数(例如 wrapper)。
    • 这一阶段负责装饰器的注册或配置。
    • 示例:
import time
from functools import wraps

def timer_decorator(func):
    print("解析时执行:注册装饰器")
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} 耗时 {time.time() - start_time:.2f} 秒")
        return result
    return wrapper

@timer_decorator
def slow_function():
    time.sleep(1)
    return "完成"

print("正在解析代码...")
slow_function()

输出:

解析时执行:注册装饰器
正在解析代码...
slow_function 耗时 1.00 秒
  • print("解析时执行:注册装饰器") 在模块加载时打印,表明装饰器的外层逻辑在解析时执行。
  1. 运行时阶段:
    • 被包装的函数(wrapper)只有在调用被装饰函数(例如 slow_function())时才会执行。
    • 在上述示例中,wrapper 的计时逻辑(记录时间并打印)在 slow_function() 调用时运行。

装饰器实战:FastAPI

装饰器在 FastAPI 框架中被广泛用于路由和事件处理。以下是具体分析。

FastAPI 的 @app.get

在 FastAPI 中,@app.get 是一个装饰器,用于将函数绑定到特定的 HTTP 路由。例如:

from fastapi import FastAPI

app = FastAPI()

@app.get("/hello")
@app.get("/v1/hello")
async def hello():
    return {"message": "你好!"}
  • 解析阶段:
    • Python 解析代码时,@app.get("/hello") 和 @app.get("/v1/hello") 调用 FastAPI.get 方法,进而调用 app.add_api_route,将 hello 函数注册到 app.routes 路由表,分别对应 /hello 和 /v1/hello。
    • 此时没有处理任何 HTTP 请求,装饰器仅完成路由配置。
  • 运行时阶段:
    • 当收到 GET /hello 或 GET /v1/hello 请求时,FastAPI 查询 app.routes,找到匹配的 hello 函数并直接调用。装饰器的逻辑(路由注册)不会再次执行。
  • 多个装饰器:
    • 多个 @app.get 装饰器按从下到上的顺序应用:
      • @app.get("/v1/hello") 将 hello 注册到 /v1/hello。
      • @app.get("/hello") 将 hello 注册到 /hello。
      • 两个路由都指向同一个 hello 函数,请求任一路径都会触发它。

FastAPI 的 @app.on_event

FastAPI 曾使用 @app.on_event 装饰器来处理启动或关闭事件。例如:

@app.on_event("startup")
async def startup_event():
    print("应用启动...")
  • 解析阶段:@app.on_event("startup") 调用 FastAPI.on_event,将 startup_event 注册到启动事件处理列表。
  • 运行时阶段:应用启动时,FastAPI 调用所有注册的启动事件函数。

注意:@app.on_event 在 FastAPI 新版本中已废弃,推荐使用 lifespan 机制。例如:

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    print("应用启动...")
    yield
    print("应用关闭...")

app = FastAPI(lifespan=lifespan)

关于装饰器的关键点

  • 不限于函数:装饰器可以应用于函数、方法或类,只要它们是可调用对象(类需实现 __call__)。
  • 多个装饰器:多个装饰器按从下到上的顺序应用(由内向外)。例如:
@decorator1
@decorator2
def my_function():
    print("函数执行")
  • decorator2 先包装 my_function,decorator1 再包装 decorator2 的结果。

  • 解析与运行时:

    • 解析时:执行装饰器的外层逻辑(注册、配置)。
    • 运行时:执行包装逻辑(例如计时、路由处理)。
  • FastAPI 特性:@app.get 在解析时注册路由(调用 app.add_api_route),运行时直接调用目标函数。

结论

Python 的 @ 装饰器是一种优雅的工具,能够增强代码的模块化和可重用性。无论是记录函数执行时间、注册 FastAPI 路由,还是实现权限控制,装饰器都提供了简洁的方式在不修改原始代码的情况下添加功能。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐