Odoo 开发

Odoo recordset 不是 list:+、|、&、- 背后的去重、顺序和语义边界

很多人会把 Odoo recordset 当成“长得像对象列表的容器”,于是对 +、|、&、- 的理解也容易套用 Python list 或 set。可官方源码里,这几种运算的去重方式、顺序保留和语义重点都不一样。本文把 recordset 集合运算讲透。

Odoo 开发
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多 Odoo 开发者在写到这类代码时,直觉会非常不稳定:

records = a + b
records = a | b
records = a & b
records = a - b

因为 recordset 既不像普通 Python list,也不像原生 set

/home/ubuntu/odoo-temp/odoo/orm/models.py 的实现看,Odoo 对这几种运算给了非常明确的语义设计:

  • + 更像拼接
  • | 更像去重后的并集
  • & 是交集
  • - 是差集

但真正容易踩坑的地方,在于:

  • 是否去重
  • 顺序是否保留
  • 重复记录怎么处理
  • 能不能跨模型

一、+ 不是并集,而是 concat

源码里 __add__() 直接调用的是 concat()

concat() 的逻辑很朴素:

  • 检查模型一致
  • 直接把各 recordset 的 _ids 线性拼起来
  • 然后 browse(ids) 返回新的 recordset

这意味着:

  • + 保留重复
  • + 保留原顺序
  • + 的重点是“连接”,不是“集合去重”

所以如果:

a = res.partner(3, 5)
b = res.partner(5, 8)
a + b

你更该把结果理解成:

res.partner(3, 5, 5, 8)

而不是 res.partner(3, 5, 8)

这点和 | 完全不同。

二、| 才是“去重后的并集”

源码里 __or__() 调用的是 union()

union() 会:

  • 先把各 recordset 的 _ids 都按顺序 append 到一个列表里
  • 最后用 OrderedSet(ids) 去重
  • browse(...) 返回 recordset

所以它的特点是:

  • 去重
  • 保留首次出现顺序
  • 仍要求同模型

这很重要,因为 Odoo 没有简单用 Python set,而是刻意保留“第一次出现时的顺序”。

所以:

a | b

不是无序集合;它仍然是一个有顺序感的 ORM recordset,只是把重复 id 折叠掉了。

三、&- 也都保留顺序,不是数学集合那种“无序结果”

差集 -

源码里的 __sub__()

  • other._ids 转成集合做 membership 判断
  • 但真正生成结果时,按 self._ids 原顺序遍历

所以:

  • a - b 会保留 a 原有顺序
  • 只删除那些也出现在 b 里的 id

交集 &

源码里的 __and__() 同样:

  • 检查同模型
  • other_ids 做包含判断
  • 但生成结果时按 self._ids 顺序筛选
  • 最后再用 OrderedSet 防止重复

所以:

  • a & b 的结果顺序以 a 为准
  • 不是随意顺序,也不是按 b

这和很多人对“交集”的数学直觉不一样。Odoo recordset 的运算始终更偏可继续操作的有序对象集合,而不是纯数学集合。

四、为什么这些细节很重要

1)你要的是保留重复,还是折叠重复?

这往往决定该用 + 还是 |

如果你只是想把两段来源拼起来,后续还要自己处理重复,+ 合适。

如果你想合并两批记录并天然去重,| 更接近意图。

2)你是否依赖当前顺序继续处理?

很多开发代码后面会接:

  • mapped()
  • filtered()
  • for rec in records

这时顺序就不是小事了。

Odoo 之所以在并集、交集、差集上都强调“first occurrence order is preserved”,本质上是在保证 recordset 仍然像一个稳定的 ORM 工作集,而不是丢给你一个无序集合。

3)不要跨模型做这些操作

无论 +|&-,源码都要求模型一致。否则就抛 TypeError

这背后的原则很合理:recordset 不只是 id 的容器,它还绑定:

  • 模型
  • env
  • 字段元数据
  • 后续 ORM 方法

跨模型混算,在 ORM 语义上根本不成立。

五、最常见的误解

误解 1:+| 只是写法不同

不对。

  • + 保留重复
  • | 去重

这在批量处理里会直接影响结果数量和后续逻辑。

误解 2:recordset 运算结果像 Python set,一定无序

也不对。Odoo 反而非常重视顺序保留,尤其是首次出现顺序。

误解 3:既然是 recordset,那重复 record 应该自动消失

也不对。只有 |& 这种路径里,源码明确做了去重;+ 不负责这件事。

六、一个实用判断法

当你要组合 recordset 时,可以先问自己两个问题:

问题 1:重复记录要不要保留?

  • 要保留 → +
  • 不要保留 → 优先考虑 |

问题 2:结果顺序你想以谁为主?

  • a - ba & b 本质上都以 a 的顺序为主
  • a | b 则是按首次出现顺序拼出一个稳定结果

只要顺序对后续逻辑有意义,就别把这些运算当成“纯集合数学”。

七、recordset 更像“有序 ORM 工作集”

这是理解这几个运算最稳的一句话:

Odoo recordset 不是 list,也不是 set,而是一个带顺序、带模型约束、可继续走 ORM 方法的工作集。

所以这四个运算真正操作的,不只是 id 本身,还包括“后续这批记录将如何继续参与 ORM 流程”。

结语

如果把源码结论压缩成一张最小心智图,就是:

  • a + b:拼接,保留重复
  • a | b:并集,去重,保留首次出现顺序
  • a & b:交集,以左侧顺序为主
  • a - b:差集,以左侧顺序为主

把这四者分清,你后面在读 Odoo 源码、写批量逻辑、组装 recordset 时,很多“结果怎么多了一条 / 少了一条 / 顺序怎么变了”的问题,就都会好解释得多。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。