[英]How to perform approximate structural pattern matching for floats and complex
我已阅读并了解浮点舍入问题,例如:
>>> sum([0.1] * 10) == 1.0
False
>>> 1.1 + 2.2 == 3.3
False
>>> sin(radians(45)) == sqrt(2) / 2
False
我也知道如何使用math.isclose()和cmath.isclose()解决这些问题。
问题是如何将这些变通方法应用于 Python 的 match/case 语句。 我希望这个工作:
match 1.1 + 2.2:
case 3.3:
print('hit!') # currently, this doesn't match
解决方案的关键是构建一个覆盖__eq__
方法并将其替换为近似匹配的包装器:
import cmath
class Approximately(complex):
def __new__(cls, x, /, **kwargs):
result = complex.__new__(cls, x)
result.kwargs = kwargs
return result
def __eq__(self, other):
try:
return isclose(self, other, **self.kwargs)
except TypeError:
return NotImplemented
它为浮点值和复值创建近似相等测试:
>>> Approximately(1.1 + 2.2) == 3.3
True
>>> Approximately(1.1 + 2.2, abs_tol=0.2) == 3.4
True
>>> Approximately(1.1j + 2.2j) == 0.0 + 3.3j
True
以下是如何在 match/case 语句中使用它:
for x in [sum([0.1] * 10), 1.1 + 2.2, sin(radians(45))]:
match Approximately(x):
case 1.0:
print(x, 'sums to about 1.0')
case 3.3:
print(x, 'sums to about 3.3')
case 0.7071067811865475:
print(x, 'is close to sqrt(2) / 2')
case _:
print('Mismatch')
这输出:
0.9999999999999999 sums to about 1.0
3.3000000000000003 sums to about 3.3
0.7071067811865475 is close to sqrt(2) / 2
雷蒙德的回答非常花哨且符合人体工程学,但对于可能更简单的东西来说似乎有很多魔力。 一个更小的版本只是捕获计算值并明确检查事物是否“接近”,例如:
import math
match 1.1 + 2.2:
case x if math.isclose(x, 3.3):
print(f"{x} is close to 3.3")
case x:
print(f"{x} wasn't close)
我还建议仅在您实际需要的地方/时使用cmath.isclose()
,使用适当的类型可以确保您的代码执行您期望的操作。
上面的示例只是用于演示匹配的最少代码,正如注释中所指出的,使用传统的if
语句可以更容易地实现。 冒着偏离原始问题的风险,这是一个更完整的示例:
from dataclasses import dataclass
@dataclass
class Square:
size: float
@dataclass
class Rectangle:
width: float
height: float
def classify(obj: Square | Rectangle) -> str:
match obj:
case Square(size=x) if math.isclose(x, 1):
return "~unit square"
case Square(size=x):
return f"square, size={x}"
case Rectangle(width=w, height=h) if math.isclose(w, h):
return "~square rectangle"
case Rectangle(width=w, height=h):
return f"rectangle, width={w}, height={h}"
almost_one = 1 + 1e-10
print(classify(Square(almost_one)))
print(classify(Rectangle(1, almost_one)))
print(classify(Rectangle(1, 2)))
不确定我是否真的会在这里使用match
语句,但希望更具代表性!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.