如何解决装饰器注入DB Session后函数签名的误导问题?
解决方法
方法一:改用上下文管理器(推荐)
直接在函数内部用with语句管理Session生命周期,既不用重复编写db.close(),函数签名也能准确反映实际需要的参数:
def do_something_with_data(data: DataClass): with Session() as db: if condition1: db.do_something1() return if condition2: db.do_something2() return if condition3: db.do_something3() return # ...
只要你的Session类实现了上下文管理器协议(比如SQLAlchemy的Session默认支持),with块结束时会自动调用db.close(),就算函数中途抛出异常也能保证Session被正确关闭,比原装饰器的实现更安全。
方法二:修复装饰器的类型标注与元数据
如果一定要保留装饰器模式,需要用functools.wraps保留原函数元数据,并通过类型提示让IDE和类型检查器识别装饰后函数的实际参数:
from functools import wraps from typing import Callable, ParamSpec, Concatenate, TypeVar P = ParamSpec('P') R = TypeVar('R') def db_depends(func: Callable[Concatenate[Session, P], R]) -> Callable[P, R]: @wraps(func) def inner(*args: P.args, **kwargs: P.kwargs) -> R: db = Session() try: return func(db, *args, **kwargs) finally: db.close() return inner @db_depends def do_something_with_data(db: Session, data: DataClass): if condition1: db.do_something1() return if condition2: db.do_something2() return if condition3: db.do_something3() return # ...
functools.wraps会把原函数的名称、文档字符串等元数据复制到装饰后的函数上,避免装饰器掩盖原函数信息。ParamSpec和Concatenate用于精确描述装饰器对函数参数的修改:告诉类型检查器,装饰后的函数接受原函数除第一个Session参数外的所有参数,这样IDE就能正确提示只需要传入data。- 新增
try...finally确保即使函数抛出异常,db.close()也会执行,修复原装饰器的潜在漏洞。
原装饰器的问题根源
原装饰器没有处理函数元数据,也没有正确的类型标注,导致IDE和类型检查器只能看到原函数的签名(要求db和data两个参数),但实际调用时装饰器已经注入了db,所以出现了签名与实际调用不符的矛盾。
内容的提问来源于stack exchange,提问作者Олексій Холостенко
相关产品推荐
相关产品推荐

