Match case statement in Python 3.10

Structural pattern matching

Match case statement is the most awaited feature in Python. It will be released with the alpha6 or alpha7 version of Python 3.10. PEP 619 is the go to place for developers to check the release schedule. Developer community is split into those who appreciate this feature and those who don’t.

Where it started ?

In PEP 3103, the proposal to add a switch case type statement in Python was added. Guido conducted a poll during his keynote presentation at PyCon 2007. After which he decided to reject the idea based on the poll. I’ll quickly mention the rationale behind PEP 3103. And then we will learn how PEP 636 was introduced with this feature on top of PEP 3103.

Rationale (PEP 3103)

A common programming idiom is to consider an expression and do different things depending on its value. This is usually done with a chain of if/elif tests; I’ll refer to this form as the “if/elif chain”. There are two main motivations to want to introduce new syntax for this idiom:

  • It is repetitive: the variable and the test operator, usually ‘==’ or ‘in’, are repeated in each if/elif branch.
  • It is inefficient: when an expression matches the last test value (or no test value at all). It is compared to each of the preceding test values.

PEP 636 Roadmap

PEP 636 was approved after few rejections of other PEPs. The proposal started with PEP 622 replaced by PEP 634 released in PEP 636. You might know switch case statement from other language like C, C++, Java etc . This is named structural pattern matching in Python. Now, the final version is accepted. This will be released in alpha6 or alpha7 version of Python 3.10.

What is match case & how to use it ?

In Python, as we discussed above that match case is termed as Structural Pattern Matching. And there is more functionality added to this rather than a normal Switch/Case statement. The pattern matching process takes as input a pattern (following case) and a subject value (following match).

  • Subject value: A subject value is followed after the match(switch in other language) keyword.
  • Pattern: A pattern is followed after the case keyword. This pattern is match with (or against) the subject value.

The primary outcome of pattern matching is success or failure. In case of success we may say “the pattern succeeds”, “the match succeeds”, or “the pattern matches the subject value”. Let’s try to understand the terms with an example.

command = input("What features are coming next in Python 3.10 ? ")
match command.split():
    case [action, obj]:
         ... # interpret action, obj

command.split() is the subject here, the value of which will be matched against or with the case statement. [action, obj] is the pattern here that will be matched against the subject.

Usage

The match statement evaluates the “subject” (the value after the match keyword), and checks it against the pattern (the code next to case). A pattern is able to do two different things:

  • Verify that the subject has certain structure. In this case, the [action, obj] pattern matches any sequence of exactly two elements. This is called matching
  • It will bind some names in the pattern to component elements of the subject. In this case, if the list has two elements, it will bind action = subject[0] and obj = subject[1].

If there’s a match, the statements inside the case block will be executed with the bound variables. If there’s no match, nothing happens and the statement after match is executed next.

The response from the input can be multiple words, therefore command.split() will return multiple subjects or subjects with different length in case of different inputs. We can have a multiple match statement regarding that to handle such scenarios.

match command.split():
    case [action]:
        ... # interpret single-verb action
    case [action, obj]:
        ... # interpret action, obj

Wildcard in match pattern

An important functionality we have used in any Switch case statement is the default block. A default block is executed when all the case statements failed to match. The same is supported in Python with a wildcard pattern.

For ex.. we may want to print an error message saying that the command wasn’t recognized when all the patterns fail.

match command.split():
    case ["quit"]:
        ... # Code omitted for brevity
    case ["go", direction]:
        ...
    case ["drop", *objects]:
        ...
    ... # Other cases
    case _:
        print(f"Sorry, I couldn't understand {command!r}")

This special pattern which is written _  (and called wildcard) always matches but it doesn’t bind any variables.

Note that this will match any object, not just sequences. As such, it only makes sense to have it by itself as the last pattern (to prevent errors, Python will stop you from using it before).

In the above example, we can notice that Match also work on keyword arguments and any object as well. That is why , I said in the beginning that this Pattern matching is much more than a regular switch case statement.

Matching special values in Pattern

Let’s look a bit deep into what that “much more than a regular switch case” mean ? Let’s consider the below example Python documentation.

match command.split():
    case ["quit"]:
        print("Goodbye!")
        quit_game()
    case ["look"]:
        current_room.describe()
    case ["get", obj]:
        character.get(obj, current_room)
    case ["go", direction]:
        current_room = current_room.neighbor(direction)
       # The rest of your commands go here

In the above case statements/patterns the first case will match only if the output from command.split() is one word and that is “quit”. Similarly for second case pattern , the output from command.split() need to be only one word and that is “look” for the pattern to patch.

An interesting thing to note here is the third case where a pattern like [“get”, obj] will match only 2-element sequences that have a first element equal to “get”. It will also bind obj = subject[1].

As you can see in the go case, we also can use different variable names in different patterns. Literal values are compared with the == operator except for the constants True, False and None which are compared with the is operator.

Matching OR patterns

We may sometimes want to have several patterns resulting in the same outcome. For example, we might want the commands north and go north be equivalent. You may also desire to have aliases for get X, pick up X and pick X up for any X.

The | symbol in patterns combines them as alternatives. We could for example write:

match command.split():
     ... # Other cases
    case ["north"] | ["go", "north"]:
        current_room = current_room.neighbor("north")
    case ["get", obj] | ["pick", "up", obj] | ["pick", obj, "up"]:
        ... # Code for picking up the given object

This is called an or pattern and will produce the expected result. Patterns are tried from left to right; this may be relevant to know what is bound if more than one alternative matches. An important restriction when writing or patterns is that all alternatives should bind the same variables. So a pattern [1, x] | [2, y] is not allowed because it would make unclear which variable would be bound after a successful match. [1, x] | [2, x] is perfectly fine and will always bind x if successful.

And there is much more like Capturing matched sub-patterns , Adding conditions to patterns, Matching positional attributes , Matching against constants and enums etc…

Leave a Reply

Your email address will not be published. Required fields are marked *