很多人第一次见到 @api.depends_context,会把它理解成一种“更高级的 depends 注释”。
这理解不算全错,但远远不够。
如果你顺着 /home/ubuntu/odoo-temp/odoo/orm/decorators.py、odoo/orm/fields.py 和 odoo/orm/environments.py 往下看,会发现它真正做的事是:
告诉 ORM:这个字段的值,不只依赖记录字段,还依赖某些 context 键;所以缓存和重算都必须把这些上下文一起算进去。
也就是说,它不是“写给人看的提示”,而是直接进入框架行为。
一、为什么普通 @api.depends 不够
@api.depends('field_a', 'field_b') 描述的是:
- 这个计算字段依赖哪些数据库字段
- 哪些字段变化后要把它标脏并重算
但有一类字段,值的变化并不是由业务字段改动触发,而是由当前上下文触发,比如:
- 当前语言
lang - 当前公司
company/company_id - 当前用户
uid - 当前目标币种、日期等临时参数
最典型的例子就是汇率、显示名称、翻译后的视图结构、附件二进制大小显示等。
这些值可能在同一条记录、同一时刻、不同上下文里都不一样。
如果你只写 @api.depends,ORM 会以为:
- 只要记录没变,值就该一样
这时缓存就会把“上一个上下文算出来的值”错当成“所有上下文通用的值”。
二、depends_context 真正影响的是缓存键
在 fields.py 里,字段会收集两类依赖:
dependsdepends_context
随后这些上下文依赖会被放进 registry 的 field_depends_context 中,由 environment 在缓存时参与 key 计算。
这个设计非常关键。
因为 Odoo 的字段缓存不是“字段名 + 记录 id”这么简单。对于声明了 depends_context 的字段,缓存实际上还要区分:
- 这是哪种语言算出来的
- 这是哪家公司上下文算出来的
- 这是哪个用户视角算出来的
- 以及你显式声明的其他 context 键
所以可以把它理解成:
@api.depends_context的本质,是让“同一个字段”在缓存里拥有多套上下文分桶。
不声明,就会串桶;声明了,ORM 才知道该分桶。
三、为什么漏写后最常见的症状是“偶发错值”
这类 bug 最烦人的地方在于,它常常不是每次都错,而是:
- 管理员看起来正常
- 普通用户偶尔不对
- 切英文后又正常
- 切回中文还是旧值
- 多公司来回切后结果飘忽
原因不是玄学,而是缓存复用了不该复用的值。
举个直白的理解方式:
- 第一次在
lang=zh_CN下算出值 A - 结果被缓存
- 第二次你在
lang=en_US下读同一字段 - 如果没声明
depends_context('lang') - ORM 可能直接把 A 当缓存命中返回
于是你就得到一个“逻辑正确、时机错误”的旧值。
这种问题特别像脏缓存,所以很多人会先怀疑:
- 浏览器缓存
- QWeb 缓存
- nginx
- workers 不同步
但根因其实在字段缓存建模阶段就埋下了。
四、哪些 context 键最值得警惕
从 Odoo 自身源码里看,最常见、也最容易漏的是这几个:
1. lang
只要结果会受翻译、显示名、本地化文本影响,就要高度警惕。
像 ir.ui.view 里一些 arch 读取逻辑,天然就与语言、翻译开关相关。
2. company / company_id
多公司场景里,同一字段值、同一规则、同一汇率语义都可能因公司而变。
res.currency 里就大量依赖公司上下文。
3. uid
如果字段结果和“当前是谁在看”有关,比如权限过滤、个性化显示、用户相关标识,漏写 uid 很容易在管理员与普通用户之间串结果。
4. 临时业务参数
例如:
dateto_currencybin_size- 你自己模块里塞进 context 的业务键
只要字段读值会显式使用它们,就要考虑是否声明。
五、它不只是影响“读缓存”,也影响重算传播
depends_context 很容易被误以为只影响读取缓存,其实它也影响框架对字段依赖关系的理解。
在 registry 收集依赖时,字段除了普通 depends 图,还会额外记录 context 依赖。
这意味着 Odoo 会知道:
- 这个字段不是纯记录内函数
- 不能把某一次上下文下的结果当成全局稳定值
换句话说,它不是简单的“缓存失效提示”,而是在告诉 ORM:
这是一个带上下文维度的计算字段。
六、开发里最容易犯的 4 个误区
误区 1:只要字段是 compute,就一律写 @api.depends
错。
有些字段的差异不是字段变化引起,而是上下文变化引起。只写 depends,描述不完整。
误区 2:context 是临时参数,不会影响缓存
恰恰相反。只要计算结果读了 context,它就已经参与了“值是否可复用”的判断。
误区 3:先不写,反正测出来没问题
这种 bug 在单用户、单公司、单语言开发环境里非常容易潜伏。
一到:
- 多 worker
- 多公司
- 多语言
- 多角色
就会暴露。
误区 4:所有 context 键都一股脑声明进去
也不对。
声明过多会让缓存分桶变碎,降低复用率。真正应该声明的是:
- 确实参与计算结果的键
- 而不是上下文里“顺手带着”的所有键
七、实战里怎么判断该不该写
可以用一个很实用的问题来判断:
如果同一条记录,在不同 context 下允许得到不同正确结果,那这个 compute 字段就该认真评估
@api.depends_context。
再具体一点,检查这几件事:
- 计算函数里有没有
self.env.context.get(...) - 有没有依赖
env.company、env.user、语言、翻译状态 - 有没有调用那些本身依赖 context 的下游方法
- 结果是否允许在不同公司 / 语言 / 用户之间变化
如果答案是“有”,那多半不该只停在 @api.depends。
八、一句话记忆
可以把 @api.depends 和 @api.depends_context 分成两句话来记:
@api.depends:哪几个字段变了,我要重算@api.depends_context:哪几个上下文变了,同一字段也不能复用旧值
前者描述“数据依赖”,后者描述“视角依赖”。
结语
@api.depends_context 最容易被低估的地方在于:它看起来像语法补充,实际上却在改 ORM 对字段值稳定性的定义。
你一旦理解它控制的是:
- 缓存键怎么分桶
- 结果能不能跨上下文复用
- 字段为什么会在不同语言/公司/用户下读出不同值
很多“偶发错值”“切语言后不刷新”“多公司读到旧结果”的问题,就会从玄学变回工程问题。
而这,正是它真正值钱的地方。
DISCUSSION
评论区