Your app reads a command from a form field and decides what to do. The usual code compares one value again and again:
def handle(command: str) -> str:
if command == "start":
return "Starting"
elif command == "stop" or command == "quit":
return "Stopping"
else:
return f"Unknown: {command}"
A match statement says the same thing as a list of cases.
One match, several cases
def handle(command: str) -> str:
match command:
case "start":
return "Starting"
case "stop" | "quit":
return "Stopping"
case _:
return f"Unknown: {command}"
The value after match is the subject. Each case holds a
pattern. Python tries the cases from top to bottom. It runs the body of
the first case whose pattern matches and skips the rest. If no case
matches, nothing happens, and the code after the match runs.
"start" is a literal pattern. It matches when the subject equals
"start". "stop" | "quit" matches either string. _ is the
wildcard. It matches anything and stores nothing, so it works like
else at the end.
Store what you matched
A bare name is a capture pattern. Like _, it matches anything, and it
also stores the subject in that name:
case other:
return f"Unknown: {other}"
A type name followed by () checks the type, the way isinstance does.
Add as and a name to keep the value:
def describe(value: int | str) -> str:
match value:
case int() as n:
return f"number {n}"
case str() as text:
return f"text {text!r}"
This is a class pattern. Later exercises use it with your own classes,
where it can also pull out attributes. mypy uses the check too. Inside the
first case, it knows that n is an int.
Add a condition with a guard
A guard is an if condition after the pattern. The case matches only
when the pattern matches and the condition is true. Otherwise Python moves
on to the next case.
def status_label(code: int) -> str:
match code:
case 200:
return "ok"
case 404 | 410:
return "gone"
case int() as n if n >= 500:
return "server error"
case _:
return "other"
print(status_label(503)) # server error
print(status_label(302)) # other
A bare name never compares
This looks as if it checks the status against OK:
OK = 200
def label(status: int) -> str:
match status:
case OK:
return "ok"
return "not ok"
It does not. OK is a capture pattern, so it matches every status and
stores it in a new local variable called OK. label(404) returns "ok".
To compare, write the literal, as in case 200:. Or use a dotted name. A
name with a dot in it, such as an enum member, is a value that Python looks
up and compares with the subject:
from enum import IntEnum
class Status(IntEnum):
OK = 200
NOT_FOUND = 404
def label(status: int) -> str:
match status:
case Status.OK:
return "ok"
case Status.NOT_FOUND:
return "missing"
case _:
return "other"
Python catches part of this mistake for you. A capture pattern with no guard matches everything, so it has to be the last case. If another case comes after it, the file does not run at all:
SyntaxError: name capture 'OK' makes remaining patterns unreachable
Check your understanding
The last case of a match on an int is case MAX_RETRIES:, where MAX_RETRIES = 3 is a module constant. What does that case match?
Show answer
Hide answer
Every value. A bare name is a capture pattern, so the case matches any
subject and stores it in a new local variable called MAX_RETRIES. To
compare with the constant, write case 3:, use a dotted name such as
Limits.MAX_RETRIES, or add a guard, as in case n if n == MAX_RETRIES:.
Going further
Python 3.10 added match. The next exercises add patterns for lists, such
as [first, *rest], for dictionaries, such as {"type": "user"}, and class
patterns that read attributes from your own classes. The
pattern matching tutorial walks
through every kind of pattern.