组合与性能¶
predylogic 提供两种把原子规则拼成谓词树的写法,语义完全相同,差别在构建开销与可读性。沿用快速开始里的规则。
两种组合写法¶
- 运算符重载
&(与)、|(或)、~(非) all_of(...)/any_of(...):接收一个谓词序列,一次性构建扁平的 N-ary 节点。
两者语义等价,对任意输入结果相同:
op = is_safe() & (is_high_value(2000) | in_regions(["US", "EU"]))
fn = all_of([is_safe(), any_of([is_high_value(2000), in_regions(["US", "EU"])])])
差别在构建:a & b & c ... 是左结合的二叉链,每个 & 生成一个二叉节点(各为 O(1),整条链 O(N));all_of([...]) 则按列表批量分配。组合大量同构(纯 AND 或纯 OR)的规则时, 尤其从配置批量加载。all_of / any_of 构建更省、意图更清晰。少量规则随意选,可读性优先。
N-ary 展平¶
AND / OR 满足结合律(构成幺半群),嵌套因此可以在编译期压平:all_of([a, all_of([b, c])]) 与 all_of([a, b, c]) 等价,编译后都是一个扁平的 N-ary 节点,而非二叉嵌套。& 写出的左结合二叉链也会被编译器同样地收集成扁平 N-ary。
这既避免了深树递归,也让运行时退化成线性的一遍 and / or。原理与基准见 ADR 002。
快路径:编译到 bytecode¶
谓词树不是逐节点解释执行的。首次调用时(默认场景:开启短路、不开 trace),它被编译成 Python 字节码,并按 (trace, short_circuit, fail_skip) 缓存;之后的调用直接走缓存的字节码,跑原生 opcode(JUMP_IF_FALSE 等),开销接近手写的 and / or。
据 ADR 002 的 CodSpeed 基准,默认模式的运行时在原生 Python(def)的约 7% 以内,而深链(depth 1000)下的构建甚至快于手写。开启 trace 或关闭短路会走另一条慢路径。所以不要把它们加在快路径上。
什么时候该关心¶
大多数场景不必操心。业务规则通常只有十几条,且真实执行往往是 I/O 密集(查库、取用户画像),引擎本身的微秒级开销相对网络延迟可忽略。
一次性组合大量同构规则(尤其批量加载配置)时,优先选 all_of / any_of,构建成本更低、意图更清晰。成百上千层是压力测试而非常态,引擎能处理任意深度(迭代编译,无递归上限),把这当作可靠性保证而非日常需求即可。