Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 636 – Structural Pattern Matching: Tutorial

PEP 636 – Structural Pattern Matching: Tutorial

Author:
Daniel F Moisset <dfmoisset at gmail.com>
Sponsor:
Guido van Rossum <guido at python.org>
BDFL-Delegate:

Discussions-To:
Python-Dev list
Status:
Final
Type:
Informational
Created:
12-Sep-2020
Python-Version:
3.10
Post-History:
22-Oct-2020, 08-Feb-2021
Resolution:
Python-Committers message

Table of Contents

Abstract

This PEP is a tutorial for the pattern matching introduced by PEP 634.

PEP 622 proposed syntax for pattern matching, which received detailed discussion both from the community and the Steering Council. A frequent concern was about how easy it would be to explain (and learn) this feature. This PEP addresses that concern providing the kind of document which developers could use to learn about pattern matching in Python.

This is considered supporting material for PEP 634 (the technical specification for pattern matching) and PEP 635 (the motivation and rationale for having pattern matching and design considerations).

For readers who are looking more for a quick review than for a tutorial, see Appendix A.

Tutorial

As an example to motivate this tutorial, you will be writing a text adventure. That is a form of interactive fiction where the user enters text commands to interact with a fictional world and receives text descriptions of what happens. Commands will be simplified forms of natural language like get sword, attack dragon, go north, enter shop or buy cheese.

Matching sequences

Your main loop will need to get input from the user and split it into words, let’s say a list of strings like this:

command = input("What are you doing next? ")
# analyze the result of command.split()

The next step is to interpret the words. Most of our commands will have two words: an action and an object. So you may be tempted to do the following:

[action, obj] = command.split()
... # interpret action, obj

The problem with that line of code is that it’s missing something: what if the user types more or fewer than 2 words? To prevent this problem you can either check the length of the list of words, or capture the ValueError that the statement above would raise.

You can use a matching statement instead:

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

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 your 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 your 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.

Note that, in a similar way to unpacking assignments, you can use either parenthesis, brackets, or just comma separation as synonyms. So you could write case action, obj or case (action, obj) with the same meaning. All forms will match any sequence (for example lists or tuples).

Matching multiple patterns

Even if most commands have the action/object form, you might want to have user commands of different lengths. For example, you might want to add single verbs with no object like look or quit. A match statement can (and is likely to) have more than one case:

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

The match statement will check patterns from top to bottom. If the pattern doesn’t match the subject, the next pattern will be tried. However, once the first matching pattern is found, the body of that case is executed, and all further cases are ignored. This is similar to the way that an if/elif/elif/... statement works.

Matching specific values

Your code still needs to look at the specific actions and conditionally execute different logic depending on the specific action (e.g., quit, attack, or buy). You could do that using a chain of if/elif/elif/..., or using a dictionary of functions, but here we’ll leverage pattern matching to solve that task. Instead of a variable, you can use literal values in patterns (like "quit", 42, or None). This allows you to write:

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

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 multiple values

A player may be able to drop multiple items by using a series of commands drop key, drop sword, drop cheese. This interface might be cumbersome, and you might like to allow dropping multiple items in a single command, like drop