match statements (intro)
Replace long if/elif chains with match, using literal, capture, wildcard and alternative patterns plus guards.
What you will learn
- Write a match statement with literal and wildcard patterns
- Combine patterns with | and capture values with a name
- Add an if guard to a case
- Decide when match is clearer than if/elif
On this page
Requires version 3.10 or later A long if/elif chain that compares one value against many possibilities gets noisy. Python 3.10 added the match statement, called structural pattern matching, to say the same thing more directly (PEP 634 is the specification, PEP 636 the tutorial). It does a lot more than a switch in other languages, but this lesson covers the everyday core. Everything in this lesson needs Python 3.10 or later.
Literal patterns, alternatives and guards
The word after match is the subject. Python tries each case from top to bottom and runs the block of the first pattern that fits, then leaves the whole match. There is no fall-through, so you never write break.
The examples loop over a few values so you can see every case run. The text "200 302 404 503 418".split() makes a list of five words, and int(text) turns each into a number.
for text in "200 302 404 503 418".split(): status = int(text) match status: case 200: print(status, "ok") case 301 | 302: print(status, "redirect") case 404: print(status, "not found") case code if code >= 500: print(status, "server error", code) case _: print(status, "other")200 ok302 redirect404 not found503 server error 503418 otherThe patterns above:
- Literal pattern (
case 200): matches when the subject equals that number, string,True,FalseorNone. - Or-pattern (
case 301 | 302): matches if either alternative matches. - Capture pattern (
case code): a bare name matches anything and binds it to that name, herecode. - Guard (
if code >= 500): an extra condition after the pattern. The case runs only when the pattern fits and the guard is truthy. - Wildcard (
case _): matches anything and binds nothing. Use it last, as the “else”.
Matching shapes
Patterns can also describe the shape of a sequence. This is where match pulls ahead of if. A text-adventure command parser is a good example, since a command is a list of words. command.split() turns "go north" into the list ["go", "north"], and a sequence pattern with the same square brackets matches it.
for line in "look;go north;go up;dance;quit".split(";"): print(">", line) match line.split(): case ["quit"]: print("Goodbye.") case ["look"]: print("You see a dusty room.") case ["go", ("north" | "south" | "east" | "west") as direction]: print("You walk", direction + ".") case ["go", _]: print("You cannot go that way.") case _: print("I don't understand", repr(line) + ".")> lookYou see a dusty room.> go northYou walk north.> go upYou cannot go that way.> danceI don't understand 'dance'.> quitGoodbye.A sequence pattern like ["go", _] matches a two-item list whose first item is the string "go"; the second can be anything. Each name in the pattern is bound as the match happens. ("north" | "south" | "east" | "west") as direction matches any of four literals and also keeps the matched value under a name with as.
Order matters, as in if/elif. ["go", _] sits after the direction case, so valid directions are handled first.
Capturing the rest of a sequence
A starred name in a pattern captures any number of remaining items as a list, and the pattern [] matches an empty list, which is what "".split() gives.
for line in "take lamp;take a b;".split(";"): print(">", line) match line.split(): case ["take", item]: print("You pick up the", item + ".") case ["take", *items] if items: print("One thing at a time:", ", ".join(items) + ".") case []: print("Say something.") case _: print("I don't understand.")> take lampYou pick up the lamp.> take a bOne thing at a time: a, b.>Say something.["take", item] matches exactly two words and captures the second as item. ["take", *items] uses a star to capture the remaining words, and the guard if items makes sure at least one word follows. The last line of the input is empty, so "".split() is [] and the [] case answers.
When to use match
Reach for match when you are branching on the value or shape of a single subject: command parsers, message types, status codes, small tokenisers. Keep if/elif for conditions that compare different things or involve ranges and calculations. Both are fine for a few branches, so choose whichever reads better. The pattern language goes much deeper (class patterns, mappings, nested shapes), and an advanced lesson picks that up.
Common pitfalls
- Treating a bare name as a constant to compare against.
case MISSING:does not test for equality with a variable namedMISSING. It captures anything into a new nameMISSING. To match a named constant, use a dotted name such asStatus.MISSING, which is compared by value. - Putting a capture or
_before other cases. A pattern that matches everything makes later cases unreachable. Python refuses to compile that with aSyntaxError, for example “name capture ‘x’ makes remaining patterns unreachable”. - Forgetting that a string subject is not a list of words.
match "go north":withcase ["go", _]does not match, because strings are deliberately not treated as sequences by sequence patterns. Split the text first. - Using it on Python older than 3.10.
matchis aSyntaxErrorthere. Checkpython --version. - Expecting fall-through or a
break. After one case runs, thematchis done.
Use match to print weekday for the numbers 1 to 5 and weekend for 6 and 7. Loop over the numbers 1 to 7 and print each number followed by its label. Use an or-pattern for the weekend.
Input: none. Expected output: seven lines from 1 weekday to 7 weekend.
Show solution
for day in range(1, 8): match day: case 6 | 7: print(day, "weekend") case _: print(day, "weekday")1 weekday2 weekday3 weekday4 weekday5 weekday6 weekend7 weekendThe or-pattern 6 | 7 catches both weekend days, and the wildcard handles everything else.
Extend the parser with two new commands: "drop <item>", which prints You drop the <item>., and "inventory", which prints You carry nothing.. Test with the commands drop lamp, inventory and drop on its own, which should fall through to the “I don’t understand” case.
Input: the commands drop lamp, inventory, drop. Expected output:
You drop the lamp.You carry nothing.I don't understand 'drop'.Show solution
for line in "drop lamp;inventory;drop".split(";"): match line.split(): case ["inventory"]: print("You carry nothing.") case ["drop", item]: print("You drop the", item + ".") case _: print("I don't understand", repr(line) + ".")You drop the lamp.You carry nothing.I don't understand 'drop'.["drop", item] needs exactly two words, so a bare drop is one word and skips to the wildcard. In the full parser, add these two cases anywhere before case _.
Summary
- Requires version 3.10 or later
match subject:followed bycase pattern:blocks runs the first case that fits; there is no fall-through. - Literals compare by value,
|offers alternatives, a bare name captures,_is the wildcard, andifadds a guard. - Sequence patterns such as
["go", direction]match by shape and bind names, which makes command parsing tidy.
That completes the control-flow module. Next comes collections, starting with lists.