很多 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 - b、a & b本质上都以a的顺序为主a | b则是按首次出现顺序拼出一个稳定结果
只要顺序对后续逻辑有意义,就别把这些运算当成“纯集合数学”。
七、recordset 更像“有序 ORM 工作集”
这是理解这几个运算最稳的一句话:
Odoo recordset 不是 list,也不是 set,而是一个带顺序、带模型约束、可继续走 ORM 方法的工作集。
所以这四个运算真正操作的,不只是 id 本身,还包括“后续这批记录将如何继续参与 ORM 流程”。
结语
如果把源码结论压缩成一张最小心智图,就是:
a + b:拼接,保留重复a | b:并集,去重,保留首次出现顺序a & b:交集,以左侧顺序为主a - b:差集,以左侧顺序为主
把这四者分清,你后面在读 Odoo 源码、写批量逻辑、组装 recordset 时,很多“结果怎么多了一条 / 少了一条 / 顺序怎么变了”的问题,就都会好解释得多。
DISCUSSION
评论区