Capture makes remaining patterns unreachable
Asked Answered
R

2

43

Why does this code fail:

OKAY = 200
NOT_FOUND = 404
INTERNAL_SERVER_ERROR = 500

match status:
    case OKAY:
        print('It worked')
    case NOT_FOUND:
        print('Unknown')
    case INTERNAL_SERVER_ERROR:
        print('Out of service')
    case _:
        print('Unknown code')

It generates this error message:

  File "/Users/development/Documents/handler.py", line 66
    case OKAY:
         ^^^^
SyntaxError: name capture 'OKAY' makes remaining patterns unreachable

What does that error message mean and how do I fix the code to make it work?

Redon answered 13/5, 2021 at 19:40 Comment(2)
Does this answer your question? How to use values stored in variables as case patterns?Abed
The answer is similar but the question is different. The question here is about the cause meaning of error message. The question there is how to use variables in case clauses.Redon
R
47

Cause of the problem

A variable name in a case clause is treated as a name capture pattern.

It always matches and tries to make an assignment to the variable name. This is almost certainly not what was intended.

Because the first matching case wins and because case OKAY always matches, the other case clauses will never be checked.

That explains the error message:

SyntaxError: name capture 'OKAY' makes remaining patterns unreachable

Key to solving the problem

We need to replace the name capture pattern with a non-capturing pattern such as a value pattern that uses the . operator for attribute lookup. The dot is the key to matching this a non-capturing pattern.

There are many ways to achieve this. One is to put the names in a class namespace:

class ResponseCode:
    OKAY = 200
    NOT_FOUND = 404
    INTERNAL_SERVER_ERROR = 500

Now, case ResponseCode.NOT_FOUND: ... is a value pattern (because of the dot) and won't capture.

Another way to achieve the same effect is to move the constants into their own module and refer to them using the dot:

import response_code

match status:
   case response_code.OKAY: ...
   case response_code.NOT_FOUND: ...
   case response_code.INTERNAL_SERVER_ERROR: ...

Besides creating a class or a module, it is also possible to create an integer enumeration for the same effect:

from enum import IntEnum

class ResponseCode(IntEnum):
    OKAY = 200
    NOT_FOUND = 404
    INTERNAL_SERVER_ERROR = 500

For HTTP response codes, an integer enumeration has already be created for you in the HTTPStatus class found in the standard library.

Example solution

Here is a worked out solution to the original problem. The presence of the . for the enum attribute lookup is the key to match and case recognizing this as a value pattern:

from http import HTTPStatus

status = 404

match status:
    case HTTPStatus.OK:
        print('It worked')
    case HTTPStatus.NOT_FOUND:
        print('Unknown')
    case HTTPStatus.INTERNAL_SERVER_ERROR:
        print('Out of service')
    case _:
        print('Unknown code')
        
Redon answered 13/5, 2021 at 19:40 Comment(8)
I just want to add, only patterns of the form of case var (like case _) will always match with literally everything. In the case of a single variable, no matter what form the subject is in match, it will be captured. Therefore, you can only avoid the error if there is only 1 case statement in the match block, or, it is the final statement. Something like case [a] will only match to a list of 1 element, but case a can match to any type of any form. Eg, match [1,2] will not match for case [a] but will for case a. So patterns of the form case a are kinda useless.Commence
N.B. case object(): also matches everything.Redon
@RaymondHettinger So it's not possible to compare 2 variables using pattern matching? Or pattern matching's target is absolutely different (in cases like this)?Cassandra
This looks like a horrible language design!!!Anthropophagi
Why does case OKAY always match? Shouldn't it match if and only if status == OKAY?Erotogenic
What if I want to match type(my_object) with a bunch of types (including built-in types and user defined classes)? Do I need to put those types in a namespace as well?Erotogenic
I think this is designed like so because the language design of this feature (if this is the right PEP) is geared primarily towards matching patterns of objects, which is a common design in other languages as well, more than for comparing values. The syntax does allow to use match blocks for comparing values, using syntax like: case t if t ..... ― that kind of syntax diverts the matching from structural to applying a conditional (if) statement. Which could be applied to this question, but would not make the code more elegant that a plain conditional blockFlavorous
In other words, it is meant for matching types and destructuring at the same time (a concept borrowed from languages like Scala and earlier ones). Destructuring entails assigning values, which can give you intuition into why assignment always happens ..... and why this language feature is not meant as the go to tool for branching by values. I'd suggest looking at examples in that PEP to get a deeper appreciation for when or how this can be useful. That said, you can use case x if ..... if you prefer to use a match block over having a regular chain of if, elif, else conditionals.Flavorous
U
0

In my advice, if you are using a question, you have to put the script like this.

match (your variable)
    case Hello:
        print('Hi back!')

to:

match (your variable)
    case 'Hello':
        print('Hi back!')
Unitary answered 2/4, 2023 at 20:17 Comment(1)
this requires the patterns to be strings. in this case they are the integer (error) codes.Hudibrastic

© 2022 - 2024 — McMap. All rights reserved.