Open malemburg opened 1 year ago
cc. @brandtbucher
Looks like it is possible: https://github.com/python/cpython/pull/108653 My PR serves as a demo.
I am not sure about all the corner cases though, because I know that numeric tower is quite complex to get right.
I'm assuming we're just talking about the numeric tower, and not about ABCs in general (like the collections)?
One wrinkle is types like Complex
and Rational
, which have more than one "part". There's a reason that we didn't implement __match_args__
on complex
and Fraction
... basically, you can get weird results when people only match one of the two positional subpatterns. Cases like these would be another point in favor of adding something like __match_args_required__
.
I'm also a bit worried about inheritance. If something inherits from these types, they now get automatically get __match_args__
, which feels a bit weird because it isn't actually part of the protocol (note that virtual subclasses wouldn't have __match_args__
unless explicitly added). I'm not an expert on ABCs, but I'm generally wary of adding new "stuff" to them for this reason. Maybe it's okay, I don't know.
While I agree this might be able to be improved, I don't think the current situation is all that bad...
match ...:
case int(x) | float(x):
print(f"An int or float: {x}")
case numbers.Rational(numerator=n, denominator=d) as x:
print(f"Some other rational number: {n}/{d}")
case numbers.Complex(real=r, imag=i) as x:
print(f"Some other complex number: {r}+{i}j")
Also, emulating the "use self for the first positional subpattern" behavior of int
and float
is a bit tricky to get right for ABCs. For a "normal" class, you can do something like:
class C:
__match_args__ = ("__self__",)
@property
def __self__(self):
return self
For the ABCs other than Complex
and Rational
, it would probably suffice to say __match_args__ = ("real",)
, since it's always the same as self
(is this always true, though?). But I'm not sure about numbers.Number
itself... I don't think registered Number
s have any attribute that you can point to and say "this is the instance's value".
So I think I'm -1
(maybe -0
if we had __match_args_required__
). I think the motivating case for int(x) | float(x)
isn't quite strong enough to justify the subtlety of the implementation's behavior.
Using as
is a normal part of pattern matching and something newbies should become comfortable using with time. I agree that it "feels good" when you can avoid it (that's why we added the "use self for the first positional subpattern" behavior for common builtins), but it's not really something to be avoided on principle alone.
Has this already been discussed elsewhere?
This is a minor feature, which does not need previous discussion elsewhere
Links to previous discussion of this feature:
No response
Proposal:
I believe this is a minor feature request, but could be wrong.
Many builtin types and user defined classes can use capture variables as first argument, e.g.
When trying to match a number which can be both float or int, you can use the as construct and an or pattern to define the match:
However, this is lengthy and rather annoying if you have to repeat this over and over again. A more natural approach would be to use the numbers module and check against numbers.Real:
Unfortunately, this fails with a TypeError:
There is a work-around, but it's not very newbie friendly:
I'm not sure whether adding a
.__match_args__ = ('x',)
to e.g. thenumbers.Real
ABC would be enough (or even correct), or whether something else needs to be done to make the above work.Linked PRs