装饰器和洋葱模式的区分

装饰器和洋葱模式的区分

执行逻辑、嵌套模型、before/after、短路能力 完全一样

真正差别就两点:

  1. 普通装饰器:直接把业务函数包死
  2. 洋葱中间件:层内部不去碰原始业务,只包装「next」这个代表下一层的回调

普通装饰器

python 复制代码
def deco(func):
    def wrapper(*args, **kwargs):
        print("before")
        res = func(*args, **kwargs)  # 这里直接硬引用真正业务函数func
        print("after")
        return res
    return wrapper

@deco
def business(): pass
  • func 就是最终业务本体。
  • 定义阶段就把 deco(business) 绑定死。

洋葱中间件层(每一层只认识next)

python 复制代码
def mw(next_func):
    def wrapper():
        print("before")
        res = next_func()   # ⚠️我根本不知道next_func是谁,它可能是另一个中间件,也可能是业务
        print("after")
        return res
    return wrapper

👉 mw 完全不知道、不关心最终的 business 存在。它只知道:我有一个叫 next_func 的东西,需要的时候调用它。

链条组装的时候才把它们串起来:

复制代码
pipeline = mw1( mw2( mw3( business ) ) )
  • mw1 的 next_func = mw2包装后的函数
  • mw2 的 next_func = mw3包装后的函数
  • mw3 的 next_func = business

每一层只和自己的直接邻居打交道,对整条链条一无所知。


用一句话区分

  • 装饰器:层直接包裹业务函数;绑定发生在写代码/导入时,静态固化。
  • 洋葱中间件:每一层只包裹"下一层",不一定是业务;链条是运行时动态拼出来的。

运行时动态拼,带来两个核心能力,装饰器很难低成本做到:

  1. 同一个业务本体,可以搭出多套不同链条
python 复制代码
# 业务还是那个business
p1 = build_pipeline([mw1, mw2], business)
p2 = build_pipeline([mw3], business)
p3 = build_pipeline([], business)

p1/p2/p3 业务不变,但中间件链路完全不一样。

用装饰器要实现,你得手动写好多个被不同装饰器包装的副本。

  1. 中间件之间完全解耦,插拔式
    你改中间件列表,不需要修改任何中间件源码,也不用碰业务代码。

但是执行流是100%同源

不管是多层@decorator,还是洋葱中间件:

  • before:外层 → 内层

  • after:内层 → 外层

  • 不调用内层函数就短路

    外层before
    内层before
    业务
    内层after
    外层after

所以你的感觉没错:运行行为几乎一样,只是组装时机和耦合度不同。

  • 装饰器:编译期静态组装,强绑定目标函数。
  • 洋葱中间件:运行时动态组装,层与层之间只通过next回调做间接连接。

每一层中间件,都不知道完整链路,只知道调用传给我的next往下走。框架负责做那个 build_pipeline 的反向循环组装。

洋葱模式一个很关键的约束:整条链条所有中间件,必须约定一套统一的入参/出参协议

不是"只有第一层有入参",而是:请求对象从头传到尾,每一层接收同一个请求对象;next 返回的也必须是约定好的响应对象。

对比普通装饰器

普通装饰器:

python 复制代码
def deco(func):
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper

*args,**kwargs兼容任意函数签名,牺牲类型安全,什么参数都能透传。

洋葱中间件不玩可变参数,强制统一结构体:

  1. FastAPI Starlette中间件:
python 复制代码
async def mw(request: Request, call_next):
    # 所有中间件,第一个参数必须是 Request
    resp = await call_next(request)
    return resp
  • 入参:Request 对象,整条链全部共用这一个对象实例
  • 出参:必须返回 Response 对象

你不能随便自定义参数,所有中间件都要遵守这套契约。

  1. DeepAgents @wrap_tool_call
python 复制代码
def mw(request: ToolCallRequest, handler):
    res = handler(request)
    return res
  • 入参固定:ToolCallRequest 结构体
  • 每一层都接收同一个 request 对象,传给下一层 handler(request)
  • 返回值也必须是工具调用的标准结果格式。

关键点:对象是同一个实例,可以原地修改

request 是可变对象,不是拷贝。

每一层拿到的是同一个对象引用,中间件可以原地修改这个结构体,下层中间件/业务直接看到修改后的内容。

示例 Starlette:

python 复制代码
async def mw1(request: Request, call_next):
    request.state.user_id = 1001  # 原地修改请求对象
    resp = await call_next(request)
    return resp

async def mw2(request: Request, call_next):
    print(request.state.user_id) # 拿到mw1设置的值,同一个对象
    return await call_next(request)

DeepAgents 同理:你可以在前面的中间件修改 request.tool_call["args"],后面中间件和真实工具直接拿到修改后的参数。

⚠️但不允许:随便新增、删减函数位置参数。不能mw接收2个参数,下一层接收3个参数,契约直接崩掉。

那参数是谁传进来的?

只有最外层外面的调用方 ,生成这个初始请求对象,传给链条第一层。

链条内部每一层,把同一个对象继续传给 next/call_next/handler。

复制代码
调用方构建 request → 传给第一层mw1
    mw1收到request,传给call_next(request) → mw2
        mw2收到同一个request,传给call_next(request) → mw3
            mw3收到同一个request,传给业务handler
业务处理完,产出response,原路逐层返回

整条链内部,不会重新构造新的请求参数,只会复用同一个结构体。

所以你的原话修正:

✅洋葱模式需要一套固定的结构体契约;初始入参由链条外部调用方生成,之后整条链路流转同一个对象实例。所有中间件必须遵守统一的入参出参类型。

带来的代价和收益

✅收益

  1. 类型清晰,框架好管控;
  2. 中间件之间传递数据靠对象属性(request.state),不用搞丑陋的函数参数透传;
  3. 每一个中间件可以独立单元测试,只要构造一个符合契约的request对象,直接调用中间件即可。

❌代价(就是你感受到的)

  1. 签名被锁死,不能随便自定义函数参数
  2. 如果想换一套参数模型,整条中间件链全部要改;
  3. 跨层传数据,只能塞到这个统一对象里面(state字段)。

和装饰器再次对比

  • 普通装饰器:*args, **kwargs 万能透传,不限制签名;但是类型差,隐式传参混乱。
  • 洋葱中间件:强契约,固定结构体对象;牺牲签名自由度,换取可插拔、统一管理。

这也是为什么 FastAPI、DeepAgents 中间件都不用裸参数,全部封装成一个大对象(Request / ToolCallRequest)。

所有附加信息、上下文、元数据,全部塞到这个对象的属性里面往下传递,而不是增加函数参数。

一句话总结

洋葱模式不是只有第一层有入参;是整条链路全部共用同一个固定契约的对象实例,外部调用方生成初始对象,层层往下透传,中间件内部原地修改对象属性来传递上下文,不能随意增减函数参数。

相关推荐
qq_452396231 小时前
第二篇:《前端架构的“道”与“术”:架构设计原则与决策框架》
前端·架构
冻柠檬飞冰走茶1 小时前
《数据结构实验指导-C++语言版》 在顺序表 list 中查找元素 x
开发语言·数据结构·c++·算法·list
Brilliantwxx1 小时前
【C++】 高阶数据结构图(1)并查集
开发语言·数据结构·c++
lancyu1 小时前
# Agent Loop:AI Agent 的唯一直心骨
开发语言·javascript·人工智能
呆呆敲代码的小Y1 小时前
10 分钟搞懂 cua:开源 AI 操作电脑基础设施,附 Python 沙箱与 Agent 上手代码
人工智能·python·开源·ai agent·awesome·llm应用·cua
工具分享1 小时前
带店托管必用爆单AI选品,一人公司轻松掌握
人工智能·python
l1258651 小时前
# RAG低延迟架构设计:从5秒到500ms的优化全链路
数据库·人工智能·python·langchain
杨丰玮4182 小时前
从零手写Java飞机躲障碍游戏|Swing绘图、鼠标跟随、计时器碰撞检测实战(五)
java·python·游戏·游戏引擎·图形渲染·动画·贴图
frjc2 小时前
阿里云 ACK 环境 Arthas 在线调试指南
开发语言·python