首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Python 还能自定义运算符?用这招实现 infix 操作符,代码酷毙了!

Python 还能自定义运算符?用这招实现 infix 操作符,代码酷毙了!

原创
作者头像
小白的大数据之旅
发布2025-11-26 10:57:27
发布2025-11-26 10:57:27
1840
举报

自定义中间运算符的 Python 实现

咱先掰扯清楚:Python 明明没给 “自定义中间运算符”(就是像 +、* 那样夹在俩数中间的符号)的官方接口,咋就能写出 “10 | 乘 | 2”“'hello' | 包含 | 'e'” 这种骚代码?其实核心就靠俩双下方法 ——roror,再套个类封装下逻辑就行。这招虽然源自 Python 2 的老配方,但 Python 3 里改改照样能用,今天咱从原理到代码、从坑到面试题,一步步给你讲透,保证每个代码复制过去都能跑!

一、先搞懂:啥是 “中间型运算符(infix operator)”?

咱平时写代码,算个乘法得写mul(10,2),检查字符串里有没有某个字符得写'e' in 'hello'。但 “中间型运算符” 是啥?就是把操作符夹在两个操作数中间,比如10 * 2里的*a + b里的+—— 这种写法更直观,尤其是处理数学逻辑或特定场景时,可读性直接拉满。

但 Python 默认只给了+-*这些内置 infix 运算符,没让咱自己定义新的(比如造个当乘法符)。那咋办?就得绕个弯子:用 Python 自带的 “位或运算符|” 当 “桥梁”,再通过类的__ror____or__方法,把a | op | b这种写法,偷偷改成op(a,b)的逻辑。

二、核心原理:为啥是__ror____or__

要搞懂这个,得先回忆 Python 的 “运算符重载” 规则:当你写a | b时,Python 会按顺序干两件事:

  1. 先看左边的a有没有__or__方法,如果有,就调用a.__or__(b),用这个结果当最终值;
  2. 如果a没有__or__,或者a.__or__(b)返回NotImplemented(表示处理不了),就会反过来调用右边b__ror__方法,也就是b.__ror__(a)

而咱要实现的a | op | b,本质是分两步走:

第一步:计算a | op
  • a是普通对象(比如 int、str),它的__or__方法只能处理同样是 int/str 的对象,没法处理咱自定义的op对象,所以会返回NotImplemented
  • 这时候 Python 就会调用op__ror__方法,把a当成参数传进去,比如op.__ror__(a)
  • 咱在__ror__里不做实际计算,只把a存起来,返回一个 “中间对象”(可以理解成 “半成品”,等着和右边的b结合)。
第二步:计算 “中间对象 | b”
  • 第一步返回的 “中间对象”,咱也让它有__or__方法;
  • 这时候调用 “中间对象.or(b)”,把之前存的a和现在的b一起传给咱定义的逻辑(比如乘法、成员检查),最后返回结果。

简单说:a | op | b → 先通过__ror__抓牢a → 再通过__or__抓牢b → 最后一起算结果。

三、代码实战:从 0 实现自定义 infix 运算符

咱先写个最基础的 “自定义运算符类”,再扩展到乘法、成员检查等场景,每个代码都能直接跑,咱一步一步来。

1. 基础框架:写一个 Infix 类

这个类是核心,负责实现__ror____or__,还要能存咱自定义的逻辑(比如乘法、包含检查)。代码里加了注释,每步干啥都写清楚:

代码语言:python
复制
class Infix:

   def __init__(self, func):

       # 初始化时,把自定义的逻辑(比如lambda)存起来

       self.func = func

   def __ror__(self, left):

       # 第一步:左边的a过来时,存下a,返回self(自己当中间对象)

       # 这里用lambda x: self.func(left, x)是为了等右边的b过来

       return Infix(lambda x: self.func(left, x))

   def __or__(self, right):

       # 第二步:右边的b过来时,调用存好的逻辑,计算left和right的结果

       return self.func(right)
2. 第一个例子:实现 “自定义乘法”

比如咱想写10 | mul | 2,结果等于10*2=20,咋弄?只需用上面的 Infix 类,把 “乘法逻辑” 用 lambda 传进去就行:

代码语言:python
复制
# 1. 定义“乘法运算符”:逻辑是x*y

mul = Infix(lambda x, y: x * y)

# 2. 测试:10 | mul | 2 → 等价于mul(10,2)

result1 = 10 | mul | 2

print("10 | mul | 2 =", result1)  # 输出:10 | mul | 2 = 20

# 再试个复杂的:(2 | mul | 3) | mul | 4 → 2*3*4=24

result2 = (2 | mul | 3) | mul | 4

print("(2 | mul | 3) | mul | 4 =", result2)  # 输出:24
3. 扩展场景:成员检查、减法、幂运算

咱再搞几个实用的,比如 “检查 a 是否包含 b”(a | contains | b)、“减法”(a | sub | b)、“幂运算”(a | power | b),代码一样简单:

代码语言:python
复制
# 1. 成员检查:a是否包含b(比如字符串、列表)

contains = Infix(lambda x, y: y in x)

# 测试:'hello'里有没有'e'?[1,2,3]里有没有4?

print("'hello' | contains | 'e' =", 'hello' | contains | 'e')  # True

print("[1,2,3] | contains | 4 =", [1,2,3] | contains | 4)    # False

# 2. 减法:a - b

sub = Infix(lambda x, y: x - y)

print("100 | sub | 30 =", 100 | sub | 30)  # 70

# 3. 幂运算:a的b次方(比如2^3=8)

power = Infix(lambda x, y: x ** y)

print("2 | power | 3 =", 2 | power | 3)  # 8

四、知识点总结:自定义 infix 运算符速查表

为了让你一目了然,咱把上面的场景整理成表格,包括用途、逻辑、代码和结果,直接抄就能用:

运算符名称

核心逻辑(lambda)

示例代码

运行结果

适用场景

乘法(mul)

lambda x,y: x * y

10 | mul | 2

成员检查(contains)

lambda x,y: y in x

'python' | contains | 'y'

减法(sub)

lambda x,y: x - y

50 | sub | 15

幂运算(power)

lambda x,y: x ** y

3 | power | 4

除法(div)

lambda x,y: x / y

20 | div | 4

取模(mod)

lambda x,y: x % y

10 | mod | 3

五、踩过的坑:常见问题 & 错误解决

咱写代码时肯定会掉坑,这里把最常见的 4 个问题列出来,告诉你咋错的、咋改:

1. 只写__or__,忘了__or__,直接报错!

错误代码

代码语言:python
复制
class BadInfix:

   def __init__(self, func):

       self.func = func

   # 只写了__or__,没写__ror__

   def __or__(self, right):

       return self.func(right)

mul = BadInfix(lambda x,y: x*y)

print(10 | mul | 2)  # 报错!

报错原因:计算10 | mul时,10(int)的__or__处理不了mul(BadInfix 对象),又没有mul.__ror__可以调用,所以 Python 扔出TypeError: unsupported operand type(s) for |: 'int' and 'BadInfix'

解决办法:必须在 Infix 类里实现__ror__方法,存下左边的操作数。

2. lambda 参数顺序写反,结果完全错!

错误代码

代码语言:python
复制
# 把lambda x,y: x-y写成了lambda x,y: y-x(参数顺序反了)

sub = Infix(lambda x, y: y - x)

print(10 | sub | 3)  # 你以为是10-3=7,实际输出3-10=-7!

错误原因:咱的 Infix 类里,__ror__存的是左边的数(比如 10),__or__传的是右边的数(比如 3),最终调用的是func(left, right)(也就是func(10,3))。如果 lambda 里写y-x,就变成了3-10,结果自然反了。

解决办法:lambda 的参数顺序必须是(left, right),要和你想实现的逻辑一致(比如减法就是x-y,x 是 left,y 是 right)。

3. 连续用运算符,没注意优先级问题!

问题代码

代码语言:python
复制
# 同时用mul和add,想算10*2 + 3 =23,但没加括号

add = Infix(lambda x,y: x+y)

print(10 | mul | 2 | add | 3)  # 实际输出多少?

实际结果:输出10 | (mul | 2 | add) | 3?不对!因为|是 “左结合” 运算符,Python 会从左往右算:

  1. 先算10 | mul → 得到中间对象 A(存了 10,逻辑是lambda x: 10*x);
  2. 再算A | 2 → 调用10*2=20,得到中间对象 B(存了 20,逻辑是lambda x:20+x);
  3. 最后算B |3 → 20+3=23。哎?这次结果对了?但如果换个顺序呢?

再试一个10 | add | 2 | mul | 3 → 按左结合算:

  1. 10 | add → 中间对象 A(存 10,逻辑lambda x:10+x);
  2. A |2 → 10+2=12,中间对象 B(存 12,逻辑lambda x:12*x);
  3. B |3 →123=36(也就是`(10+2)3=36,不是10+(2*3)=16`)。

结论:连续用自定义 infix 运算符时,|的左结合特性会影响结果。如果想改变优先级,必须加括号,比如10 | add | (2 | mul | 3),这样才是10+(2*3)=16

4. 自定义对象没实现__or__,用不了?

问题代码

代码语言:python
复制
# 自己写个类,没实现__or__和__ror__

class MyNum:

   def __init__(self, num):

       self.num = num

a = MyNum(10)

mul = Infix(lambda x,y: x.num * y.num)

print(a | mul | MyNum(2))  # 报错吗?

实际结果:不报错!因为计算a | mul时,a(MyNum)没有__or__,所以会调用mul.__ror__(a),照样能存下a;后续和MyNum(2)结合时,lambda 里用x.numy.num也能拿到值,最终输出 20。

原因:只要咱的 Infix 类实现了__ror__,不管左边是啥对象(哪怕是自定义的、没实现__or__的),都能触发__ror__,所以不用担心兼容性问题。

六、面试必问:这些问题你得会答!

如果面试官问你 “Python 咋模拟自定义 infix 运算符”,别只说 “用roror”,得把原理、优缺点说透,下面是高频问题和标准答案:

1. 为什么选__ror____or__?不用__add____sub__

答:主要是为了 “避免冲突”。+-这些运算符是 Python 里最常用的,比如你写10 + add + 2,如果用__add__模拟,很容易和原生的10+2冲突;而|(位或)平时用得少(除了处理二进制、集合),冲突概率低。另外,__ror__的 “反向调用” 特性,刚好能解决 “左边对象处理不了右边运算符” 的问题,完美适配a | op | b的场景。

2. 这种自定义 infix 运算符,实际项目里有用吗?

答:分场景:

  • 有用的场景:比如写领域特定语言(DSL),比如数学计算(a | dot | b表示向量点积)、数据处理(df | filter | condition表示过滤数据框),能让代码更直观,像 “自然语言” 一样;还有就是处理重复逻辑,比如频繁用contains(a,b),改成a | contains | b更简洁。
  • 没用的场景:通用业务代码(比如用户登录、订单处理),因为团队里可能有人没见过这种写法,反而降低可读性;简单逻辑也没必要用,比如a + b没必要改成a | add | b,纯属画蛇添足。
3. 除了__ror____or__,还有别的方法模拟 infix 吗?

答:有,但不如这个方法好用:

  • 方法 1:用第三方库,比如infix模块(pip install infix),但依赖外部库,兼容性差;
  • 方法 2:用 “中缀函数”,比如infix(lambda x,y: x*y)(10,2),但写法是infix(...),不是a op b,不够直观;
  • 方法 3:用元组或列表,比如(10, mul, 2),然后写个函数解析,但需要额外解析步骤,不如a | op | b直接。

所以__ror__ + __or__是 “无依赖、高兼容、最直观” 的方法。

4. 这种写法有什么缺点?

答:主要有 3 个:

  • 可读性因人而异:新手可能看不懂a | mul | 2是啥意思,得先理解背后的__ror__逻辑;
  • 优先级问题:|的优先级固定(比+*低),如果混合用原生运算符,得加括号,比如10 | mul | 2 + 3会变成(10*2)+3,不是10*(2+3)
  • 没法自定义新符号:比如你想造个当乘法符,用这个方法做不到,因为不是 Python 的合法运算符,只能用|当桥梁,所以本质是 “借壳”,不是 “真・自定义符号”。

七、灵魂讨论:这玩意到底有没有用?

最后咱聊聊:既然这招这么酷,为啥平时很少见人用?

其实核心是 “可读性” 和 “实用性” 的平衡。比如你自己写脚本玩,怎么酷怎么来,用a | power | 3没问题;但如果是团队协作,得考虑别人能不能看懂 —— 毕竟代码是写给人看的,不是写给机器看的。

另外,Python 的设计哲学是 “简洁明了”,这种 “黑魔法” 虽然能实现酷炫的功能,但违背了 “显式优于隐式” 的原则(PEP 20)。所以我的建议是:可以学,用来拓展思路;可以玩,自己写脚本时用;但团队项目里,除非大家都认可,否则别随便用

最后:代码汇总(直接复制就能跑)

为了方便你测试,把所有核心代码汇总在这里,复制到 Python 文件里运行,就能看到所有效果:

代码语言:python
复制
# 核心Infix类

class Infix:

   def __init__(self, func):

       self.func = func

   def __ror__(self, left):

       return Infix(lambda x: self.func(left, x))

   def __or__(self, right):

       return self.func(right)

# 1. 乘法

mul = Infix(lambda x, y: x * y)

print("10 | mul | 2 =", 10 | mul | 2)  # 20

# 2. 成员检查

contains = Infix(lambda x, y: y in x)

print("'hello' | contains | 'e' =", 'hello' | contains | 'e')  # True

print("[1,2,3] | contains | 4 =", [1,2,3] | contains | 4)    # False

# 3. 减法

sub = Infix(lambda x, y: x - y)

print("100 | sub | 30 =", 100 | sub | 30)  # 70

# 4. 幂运算

power = Infix(lambda x, y: x ** y)

print("2 | power | 3 =", 2 | power | 3)  # 8

# 5. 连续运算(加括号改变优先级)

add = Infix(lambda x, y: x + y)

print("10 | add | (2 | mul | 3) =", 10 | add | (2 | mul | 3))  # 16

这篇文章从原理到代码、从坑到面试题都讲透了,每个代码都亲测能跑。如果你还想扩展其他场景(比如自定义矩阵乘法、字符串拼接),或者想知道怎么解决 “优先级问题”,随时跟我说,咱再接着深挖!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 自定义中间运算符的 Python 实现
    • 一、先搞懂:啥是 “中间型运算符(infix operator)”?
    • 二、核心原理:为啥是__ror__和__or__?
    • 三、代码实战:从 0 实现自定义 infix 运算符
    • 四、知识点总结:自定义 infix 运算符速查表
    • 五、踩过的坑:常见问题 & 错误解决
    • 六、面试必问:这些问题你得会答!
    • 七、灵魂讨论:这玩意到底有没有用?
    • 最后:代码汇总(直接复制就能跑)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档