Literal comparison for True, False, None #139
Comments
match x:
case True: ...
case 1: ...
case 1.0: ... would work intuitively if the check for literals were def check(match, case):
return isinstance(match, type(case)) and match == case [(f'match {match} case {case}', check(match, case)) for match in (True, 1, 1.0) for case in (True, 1, 1.0)]
>>>
[('match True case True', True),
('match True case 1', True),
('match True case 1.0', False),
('match 1 case True', False),
('match 1 case 1', True),
('match 1 case 1.0', False),
('match 1.0 case True', False),
('match 1.0 case 1', False),
('match 1.0 case 1.0', True)] Notice though that def check(match, case):
if isinstance(match, bool):
return match is case
else:
return isinstance(match, type(case)) and match == case examples = [False, True, 0, 0.0, 1, 1.0]
[(f'match {match} case {case}', check(match, case)) for match in examples for case in examples]
>>>
[('match False case False', True),
('match False case True', False),
('match False case 0', False),
('match False case 0.0', False),
('match False case 1', False),
('match False case 1.0', False),
('match True case False', False),
('match True case True', True),
('match True case 0', False),
('match True case 0.0', False),
('match True case 1', False),
('match True case 1.0', False),
('match 0 case False', False),
('match 0 case True', False),
('match 0 case 0', True),
('match 0 case 0.0', False),
('match 0 case 1', False),
('match 0 case 1.0', False),
('match 0.0 case False', False),
('match 0.0 case True', False),
('match 0.0 case 0', False),
('match 0.0 case 0.0', True),
('match 0.0 case 1', False),
('match 0.0 case 1.0', False),
('match 1 case False', False),
('match 1 case True', False),
('match 1 case 0', False),
('match 1 case 0.0', False),
('match 1 case 1', True),
('match 1 case 1.0', False),
('match 1.0 case False', False),
('match 1.0 case True', False),
('match 1.0 case 0', False),
('match 1.0 case 0.0', False),
('match 1.0 case 1', False),
('match 1.0 case 1.0', True)] |
I agree that this behavior is confusing, but your proposed change could also lead to confusing behavior: if I pass say a Also, this same behavior would happen if you did a series of |
Yeah, I has something like this (an isinstance check based on the type of the literal) in an earlier version, but there were too many surprising corner cases. To start, ‘case 3.0’ should match int as well as float, according to the numeric tower. I played with isinstance and the types from the numbers module, but those are too slow. The only part here that does make sense is to use ‘is’ instead of ‘==‘ for True, False and None. |
@gvanrossum Proposing that |
Let’s add this to the next revision of the PEP, once the SC has sent us their feedback (expected this week). |
@JelleZijlstra Actually |
Oops, you're right. However, I agree with using |
@JelleZijlstra yeah I just noticed that |
@gvanrossum |
I'm beginning to think that Rust was right after all to disallow floats in patterns altogether. This made it into the PEP's Rejected ideas section, and I don't think it would be quite right for Python. E.g. what would we do if a value pattern had a float value? Come to think of it, value patterns currently strictly use One more concern with simple
|
I totally forgot about PEP 484 considering |
curious question: would the parser be able to distinguish
|
This already works the way you expect it to. |
@brandtbucher I didn't know that; thanks a lot. Using that syntax it's possible to write
however the issue with ... also now that we know how to distinguish ( |
Not exactly sure what you want without more helpful examples, but perhaps Certain built-ins like |
I though about something like json-serialization e.g.
side note: maybe this example, trivial as it is, should be part of the PEP because everyone knows json and it is a good example of the type of application where |
it actually isn't all that trivial since there are also It would really be a shame if json serialization (with all its weird edge cases) could not be written as something like:
|
You could do it with a guard, something like |
Yes. Also for None. These are all final types BTW. And I'd say only for these three we should use For the other stuff, I believe we should not change anything. And given the dark edge cases of the JSON example I don't think we should add it to the PEP (it's already too long). |
Agreed. I guess the only remaining question here is:
I'm neutral. |
Yeah, that's a tough one. For |
Well, in terms of implementation it would probably just be a new opcode that does the correct comparison at runtime, rather than separate compile-time code paths. But your rationale for not wanting to do this is sound, and I think it's easier to explain. |
Agreed. I would keep the semantics bound to syntax as close as possible, i.e. not use sometimes The idea that |
One of the early arguments for the (deferred) custom matcher feature was to be able to have matchers of the form |
@viridia I think it would be very unfortunate if we had to (import and) use a helper function for dealing with one of the primitive data structures in the language. Not to mention that we'd need to wait for the custom matcher protocol (that I am very much looking forward to) |
@viridia This probably addresses the numerous ideas brought forward for wanting to have something like |
I am putting this in the revised PEP (PEP 634, python/peps#1598), so I am labeling this as "accepted" and "fully pepped". None, True and False will be compared using |
Yep, just two lines in the compiler. I assume this is only for literals, not constant value patterns that evaluate to |
Indeed, nothing changes for value patterns. |
This has been implemented. |
I was just re-reading the spec in PEP-634 and found something which I think is misspecified around this. It's essentially about literal patterns appearing as mapping keys, i.e. a pattern like I'm guessing the reasonable and implementable answer is "yes", although our spec says |
Oh, good eye for detail! Indeed, that totally didn't get rephrased when we changed how True/False/None are matched. Given how dict key lookup works,
should totally print "foo". Can you add a separate PR to address this in PEP 634? |
Added to python/peps#1675 |
I've looked again at the Pep and also in the issues here on github but I didn't find any mention of |
Ellipsis is not a reserved word in the grammar so it is not special-cased.
If you need to match on it, you can do the following to avoid binding a
local variable named Ellipsis:
import builtins
match ...:
case builtins.Ellipsis:
...
|
@sdesch see also this comment. |
As much as I disagree with @markshannon about the general usefulness of pattern matching I agree with him (and previously didn't think about) cases like
where for
match 1:
thecase True
is selected. The reverse thing happens when one putscase int()
beforecase True
, then formatch True
thecase int()
is selected.I also noticed the discussion in #16 which mentions the handling of
True
and1
and1.0
(the latter matchingcase True
is even more surprising imho.)This is probably not what people expect (I didn't) and also not that helpful when
match
is going to be used a lot precisely in cases like the above where a function's return depends on the type of its argument (and some literal values). I'd expect people to think ofTrue
andFalse
more of constants that they want to literally match a value against and less ofsome_value == True
style equality checking. If they wanted equality checkingcase x: x == True
is much more explicit about what it does than the current behavior of using equality checking behind the scene.The text was updated successfully, but these errors were encountered: