Python中如何对浮点数和复数实现近似结构化模式匹配
浮点数match/case近似匹配实现
Python原生浮点数存在二进制存储舍入误差,以下精确等值判断均会返回不符合直觉的结果:
>>> sum([0.1] * 10) == 1.0 False >>> 1.1 + 2.2 == 3.3 False >>> from math import sin, radians, sqrt >>> sin(radians(45)) == sqrt(2) / 2 False
常规规避方案是使用math.isclose()(原生浮点数)或cmath.isclose()(复数)做近似等值判断,但match/case语句的字面量分支默认使用==做精确匹配,直接写case 3.3无法命中1.1 + 2.2的计算结果,可以通过以下两种方案实现近似匹配。
方案1:分支守卫(适配少量分支场景)
match/case支持为分支添加if守卫条件,仅当模式匹配和守卫条件同时满足时才进入对应分支,直接在守卫中调用isclose即可实现近似判断:
import math match 1.1 + 2.2: case val if math.isclose(val, 3.3): print('hit!') # 正常触发
多分支场景下可以为每个分支单独配置公差,灵活性极高:
import math val = 1.1 + 2.2 match val: case x if math.isclose(x, 0, abs_tol=1e-9): print("匹配近0值") case x if math.isclose(x, 3.3, rel_tol=1e-6): print("匹配3.3,使用宽松相对公差") case _: print("无匹配项")
方案2:自定义近似匹配类(适配多分支场景)
Python类模式匹配会通过__eq__方法做等值校验,可以把近似判断逻辑封装到自定义类中,实现和原生字面量一致的简洁写法:
import math import cmath class Approx: __match_args__ = ("expected",) # 支持位置参数传参,简化写法 def __init__(self, expected, rel_tol=1e-9, abs_tol=0.0): self.expected = expected self.rel_tol = rel_tol self.abs_tol = abs_tol def __eq__(self, actual): # 自动适配复数场景 judge = cmath.isclose if isinstance(actual, complex) else math.isclose return judge(actual, self.expected, rel_tol=self.rel_tol, abs_tol=self.abs_tol)
使用时直接在case后调用即可,不需要额外写守卫:
match 1.1 + 2.2: case Approx(3.3): print('hit!') # 正常触发 # 自定义公差直接传参即可 match 1.1 + 2.2: case Approx(3.3, rel_tol=1e-6): print('hit with loose tolerance!')
避坑提示
- 禁止通过猴子补丁修改
float.__eq__实现全局近似匹配,这会破坏Python数值系统的逻辑一致性,引发极难排查的隐性bug。 - 如果匹配目标是numpy数组、pandas数值等第三方库数值类型,只需把类中
isclose替换为对应库提供的近似判断方法(如numpy.isclose)即可,适配逻辑完全一致。
内容的提问来源于stack exchange,提问作者Raymond Hettinger
相关产品推荐
相关产品推荐

