Reputation: 33
I am computing the Lp distance functions for non-negative p's. For all but p = 0 and p = ∞ a built-in pow()
function serves well.
Before I learned about a structural pattern matching, I had used a dictionary and exception handling:
from math import sqrt, inf
distance_function = { 0.0: lambda x, y: int(x != 0.0) + int(y != 0.0),
1.0: lambda x, y: abs(x) + abs(y), # Likely a tad faster than 'pow()'
inf: lambda x, y: max(abs(x), abs(y))}
def lp_distance(x, y, p):
try: return distance_function[p](x, y)
except KeyError: return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
Some people didn't want exceptions here. So I rewrote the snippet into the following one:
def lp_distance(x, y, p):
match p:
case 0.0: return int(x != 0.0) + int(y != 0.0)
case 1.0: return abs(x) + abs(y)
# The line below triggers "SyntaxError: name capture 'inf' makes remaining patterns unreachable"
case inf: return max(abs(x), abs(y))
# But the following works:
case p if p == inf: return max(abs(x), abs(y))
case _: return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
Why case inf:
is not correct (Python v3.10.2)?
Upvotes: 3
Views: 273
Reputation: 226221
As noted by the other respondents, you can't use inf directly because that is a capture pattern. The obvious solution is to use a value pattern with a dotted lookup; however, that only works for positive infinity.
To handle other all special values like negative infinity and NaNs, you need guards:
match x:
case 1.0: ... # Exact value (literal pattern)
case 0.0: ... # Exact value (literal pattern)
case _ if math.isfinite(x): ... # Normal cases
case _ if math.isnan(x): ... # NaNs defy equality tests
case _: ... # Negative infinity
Upvotes: 3
Reputation: 50076
In a case
statement, a simple name is a pattern that captures (assigns) to that name. In contrast, a dotted name is a patterns that refers to the value of that name.
In simple terms
NAME
will always succeed and it will setNAME = <subject>
.
In simple terms
NAME1.NAME2
will succeed only if<subject> == NAME1.NAME2
Using just case inf:
means that the value to match is unconditionally assigned to the name inf
– it does not matter if the name was previously bound.
What you want instead is case math.inf:
, which means to compare against this value.
import math
def lp_distance(x, y, p):
match p:
case 0.0:
return int(x != 0.0) + int(y != 0.0)
case 1.0:
return abs(x) + abs(y)
# compare against a value by using its dotted name
case math.inf:
return max(abs(x), abs(y))
case _:
return pow(pow(abs(x), p) + pow(abs(y), p), 1.0/p)
Upvotes: 5