pythonswitch-statementsyntax-errorpython-3.10structural-pattern-matching

Capture makes remaining patterns unreachable


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?


Solution

  • 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')