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
- Tutorial
- Matching sequences
- Matching multiple patterns
- Matching specific values
- Matching multiple values
- Adding a wildcard
- Composing patterns
- Or patterns
- Capturing matched sub-patterns
- Adding conditions to patterns
- Adding a UI: Matching objects
- Matching positional attributes
- Matching against constants and enums
- Going to the cloud: Mappings
- Matching builtin classes
- Appendix A – Quick Intro
- Copyright
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]andobj = 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