很多开发者第一次遇到 stored compute 字段,心里想的是一种很直觉的模型:
- 依赖字段改了;
- compute 方法马上跑;
- 新值立刻落稳;
- 后面所有逻辑都应该看到最终值。
但 Odoo 真实实现并不是“字段一变就同步立刻算完”。
从 /home/ubuntu/odoo-temp/odoo/orm/fields.py 和 odoo/orm/models.py 看,Odoo 更像在维护一条调度链路:
- 哪些字段被改了;
- 谁依赖这些字段;
- 哪些记录要加入待重算集合;
- 什么时候真正 recompute;
- 什么时候 flush 回数据库。
所以很多“为什么它看起来没立刻更新”的现象,根源不是 compute 失效,而是你把 Odoo 想成了同步赋值脚本。
一、depends 不是“立刻执行”,而是“建立触发关系”
源码里 modified() 的注释写得很直白:
- 它要根据字段依赖树,找出哪些 stored compute 字段需要重算;
- 然后把这些字段和记录标记到待计算集合里。
也就是说,依赖声明的核心作用不是“此刻立刻计算”,而是:
当这些字段变化时,系统知道后面该重算谁。
这也是为什么你写了 @api.depends(...) 之后,看到的是“加入重算管线”,而不一定是“在你当前这行代码下一秒就完成所有最终副作用”。
二、modified() 干的是“标记与传播”
models.py 里的 modified() 很值得仔细看。
它会:
- 沿着依赖树往后找;
- 把受影响的 stored compute 字段加入
tocompute; - 对非 stored compute 更多是做缓存失效,而不是强制写库。
这意味着两件事:
1)Odoo 在区分“要不要写库”
- stored compute:需要后续重算并可能落库;
- non-stored compute:更多是失效缓存,等真正访问时再算。
2)Odoo 在区分“现在算”还是“稍后算”
字段变化之后,并不要求每次都当场把整棵依赖树完全算穿。
这正是 ORM 规模上还能跑得动的重要原因。
三、真正计算时,compute_value() 还会先处理待重算集合
fields.py 里的 compute_value() 也透露出一个很重要的信号:
- 对 stored compute 字段,会先把对应记录从待重算集合里移除;
- 再在保护上下文里执行
_compute_field_value(); - 如果失败,再把它重新放回待重算集合。
这说明 Odoo 不是“算一下就完”,而是在维护一种相对稳的事务语义:
- 先声明“这批我要开始算了”;
- 算成功,任务完成;
- 算失败,任务重新挂回去。
这也是为什么复杂 compute 出错时,你常会看到后续链路表现得像“还有东西没算干净”。
四、_recompute_recordset() 和 flush_recordset() 才是很多人忽略的时机点
_recompute_recordset() 会针对记录集,把待重算的 stored compute 字段真正处理掉。
而 flush_recordset() 又会先触发 _recompute_recordset(),再把数据刷向数据库。
因此实战里你经常会遇到这种错觉:
- Python 里刚改完字段;
- 马上看数据库,值还没变;
- 或者同一事务里某些地方看到新值,某些地方还像旧值。
这通常不是 Odoo 混乱,而是:
你正好站在“已标记待重算,但尚未完成 flush/recompute”的中间状态上。
五、为什么 stored compute 最怕副作用思维
很多人写 compute 时,会顺手干这些事:
- 写别的字段;
- 建消息;
- 改状态;
- 触发外部动作。
这很危险。
因为 compute 本来是依赖驱动的结果计算,它的执行时机可能来自:
- create;
- write;
- flush;
- 其他字段连锁重算;
- 访问字段时的懒计算场景。
如果你把它当成“稳定的一次性业务钩子”,就很容易出现:
- 重复副作用;
- 顺序错判;
- 性能爆炸;
- 调试时完全说不清到底是谁先触发谁。
六、新手最常见的三个误解
1)“stored = 立刻写库”
不对。stored 的意思是“这个值最终落库”,不是“每次字段变化都同步即时写库完成”。
2)“depends = 一改就马上跑到最终态”
不对。depends 首先是依赖声明,后面还有标记、重算、flush 这些阶段。
3)“我现在看到旧值,就是 compute 没触发”
也不一定。它可能已经进入 tocompute,只是还没在你观察的时机点完成重算。
七、实战里该怎么设计 compute 才更稳
我更建议遵守这几条:
- compute 尽量纯函数化:根据依赖算结果,少做副作用;
- 把硬校验放约束或业务方法,不要塞进 compute;
- 把通知、建单、写日志这类动作 放在明确业务事件里;
- 性能敏感时先想依赖粒度,不要让一个小变动牵动太大范围重算;
- 调试时别只盯数据库值,还要意识到 ORM 里存在“待重算中间态”。
总结
Odoo 的 stored compute 字段,本质上不是“同步赋值”,而是“依赖驱动的重算管线”。
从源码看,关键环节是:
depends定义触发关系;modified()负责传播与标记;compute_value()真正执行计算并维护待算集合;_recompute_recordset()/flush_recordset()决定什么时候把结果算实、刷实。
如果只记一句,就记这句:
stored compute 的核心不是“立刻”,而是“最终一致地被重算并落库”。
DISCUSSION
评论区