21. “|”不只是按位或,90%的人不知道

提到 Python 中的|,绝大多数开发者的第一反应都是按位或运算,5 | 3 = 7 是我们初学编程时就烂熟于心的基础。但很少有人知道,| 还能像这样用:

动画视频在《21. "|"不只是按位或,90%的人不知道》

复制代码
chain = AddOne() | MulTwo() | ToStr()
print(chain.run(5))

Python 里的 | 不只是按位或,90% 的人不知道它还能这么用。一个符号就能把多个函数串成一条流水线,数据从左到右自动流动,代码像管道一样直观。这不是什么黑魔法,而是 Python 一个极其强大但被严重低估的特性 ------运算符重载。今天我们就来彻底解锁 | 运算符的隐藏技能,顺便揭秘 LangChain 中大名鼎鼎的 LCEL 表达式语言的核心原理。

运算符不是语法特权,只是语法糖

很多人觉得 +、-、*、| 这些运算符是 Python 内置的 "特殊语法",只能用于数字、字符串等基本类型。但实际上,Python 中所有运算符都对应着一个特殊的魔法方法。当你写 a | b 时,Python 解释器会自动将其转换为 a.or(b)。

这就是整个秘密的核心。只要我们在自己的类中定义了 or 方法,就能完全自定义 | 运算符的行为。不仅是 |,理论上你可以重写所有运算符,让它们实现任何你想要的功能。

|--------|----------------|------|
| 运算符 | 对应方法 | 默认含义 |
| a & b | a.and(b) | 按位与 |
| a | b | a.or(b) | 按位或 |
| a ^ b | a.xor(b) | 按位异或 |
| ~a | a.invert() | 按位取反 |

用 | 打造你的数据流水线

要实现这样优雅的管道式调用,我们只需要定义几个简单的类。首先是所有任务的基类 Runnable,它规定了所有可运行任务必须实现 run 方法,同时重载了 or 运算符,让两个任务可以用 | 连接成一个链。

复制代码
class Runnable:
    def run(self, data):
        raise NotImplementedError
    
    def __or__(self, other):
        return Chain(self, other)

接下来是核心的 Chain 类,它本身也继承自 Runnable,这意味着一个任务链本身也是一个可运行的任务。Chain 会把所有步骤按顺序保存起来,执行时依次调用每个步骤的 run 方法,把前一步的输出作为后一步的输入。它也重载了 or 方法,支持给已经存在的链继续追加新的步骤。

复制代码
class Chain(Runnable):
    def __init__(self, *runnables):
        self.steps = list(runnables)
    
    def run(self, data):
        for step in self.steps:
            data = step.run(data)
        return data
    
    def __or__(self, other):
        return Chain(*self.steps, other)

有了这两个基类,我们就可以像搭积木一样定义各种单一职责的处理步骤了。每个步骤只需要继承 Runnable 并实现自己的 run 方法即可。

复制代码
class AddOne(Runnable):
    def run(self, data):
        return data + 1
 
class MulTwo(Runnable):
    def run(self, data):
        return data * 2
 
class ToStr(Runnable):
    def run(self, data):
        return f"结果是: {data}"

现在,我们可以用最直观的方式把这些步骤串起来:

复制代码
chain = AddOne() | MulTwo() | ToStr()
print(chain.run(5))  # 结果是: 12

| 让代码像管道一样直观,数据从左到右流动,一目了然。下次要讲的 LCEL 核心原理就是这个。