Why this matters
Many programs compare one value against a list of known values. A chat program reads a command such as start or stop. A shipping program maps a status such as SHIPPED or DELIVERED to a message. A calendar program maps a day of the week to a number.
An if chain can do this, but a long chain repeats the same variable name over and over and is hard to read. A match statement puts the value at the top and lists each possible match as a block of code under a case line.
This lesson covers match on two kinds of value. A string is a piece of text, such as "start". An enum (short for enumeration) is a class that lists a fixed set of named values, such as the seven days of the week. The enum module in the standard library builds these classes.
The text after case is called a pattern. For a plain value such as "start", a pattern means "the value is equal to this". The match statement was added in Python 3.10. The programs in this lesson were run with Python 3.14.4.
Readers who know Java will notice that Python's match is a statement, not a value, and that its blocks never run into the next one, so no break is needed. Both points are covered below.
Start with a string match
Here is a match on a string. The function takes a command and returns a reply.
Output:
starting
stopping
unknown: Start
unknown: pausePython takes the value after match, which is text, and compares it with each pattern in turn. When a pattern fits, Python runs the block under it and the match ends. The _ pattern is the fallback. It fits any value, so its block runs only when no other pattern fits. It works like default in other languages.
The f"unknown: {text}" line is an f-string. The letter f before the quotes tells Python to put the value of text into the text where the braces are.
The comparison is exact. The text "Start" with a capital S does not equal "start", so it reaches _. Python compares text letter by letter, and capital and small letters are different letters. If the program must accept both, change the text first, as Mistake 1 below shows.
Labels must be fixed values
A bare name after case, such as target, is not compared with a variable. Python reads it as a capture pattern. A capture pattern fits any value and gives that value the name. This is a trap for people who expect a comparison.
The next program has a variable called target. The variable text holds stop, but the first case still runs.
Output:
match
stopThe capture pattern target fit the value stop and stored it in the name target. The original "start" is gone. Adding a case _: after a capture pattern makes Python refuse the whole file before it runs. Python checks this when it reads the file, so the error appears with no output at all.
File "main.py", line 4
case target:
^^^^^^
SyntaxError: name capture 'target' makes remaining patterns unreachableThe path in the first line depends on where the file is saved, and the traceback lines in this lesson that show paths are shortened with .... The message means that every case after case target: could never run.
To compare with a fixed value, write a dotted name such as Mode.START. A dotted name is compared with the value. The next program puts the values in a class, which is a simple way to group names.
Output:
stoppingA plain variable name after case is never a comparison, so when the value to compare with is held in a variable, use if and == instead.
Enums with match
An enum is a class whose members are fixed. The next program defines an enum Day with the seven days. auto() gives each member a number, so the code does not have to choose them. The function day_number turns a day into its position in the week. The function kind says whether the day is a weekday or a weekend day.
Output:
MONDAY -> 1 weekday
TUESDAY -> 2 weekday
WEDNESDAY -> 3 weekday
THURSDAY -> 4 weekday
FRIDAY -> 5 weekday
SATURDAY -> 6 weekend
SUNDAY -> 7 weekend
almost weekend
6Several things happen in this program.
The patterns for enum members have the class name in front, such as Day.MONDAY. This is the dotted form from the section above. A bare MONDAY would be a capture pattern, and it would match every day.
The pattern Day.SATURDAY | Day.SUNDAY joins two patterns with |, which means "or". The block runs when either one fits. This is how one block serves two values.
The function day_number returns from inside each block. A match statement does not produce a value that can be stored, so the value is returned from the function. Mistake 4 shows what happens when the value is written on the right side of =.
Day["FRIDAY"] finds the member whose name is FRIDAY. Mistake 6 shows what happens when the name does not exist.
The for d in Day: loop visits the members in the order they are written in the class. d.name gives the member name as text.
The last line counts positions from 0. list(Day) makes a list of all the members in order, and .index(...) gives the position of one of them. SUNDAY is in position 6, so the output is 6.
Example: a file type lookup
The next example uses a string with several patterns for each result. A file name's extension is the text after the last dot, such as png in photo.png. The function below maps an extension to a category.
Output:
image document otherThe _ case is what gives an answer for every text that is not listed. Without it, the function would return None for "exe", which is a value that means "nothing was returned".
Enum matches do not check every value
An enum has a fixed list of members, but Python does not check a match against that list. The program below handles two of the three Light members. It runs without any error or warning.
Output:
goThe call action(Light.YELLOW) prints nothing. No pattern matches YELLOW, so the match does nothing and the program goes on without a sign of a problem.
To make a missing member stop the program, add a case _: block that calls assert_never from the typing module. The function raises an error when Python reaches it. The next program adds a member BLUE to Light but leaves it out of action.
Output:
go
Traceback (most recent call last):
...
File "/usr/lib/python3.14/typing.py", line 2582, in assert_never
raise AssertionError(f"Expected code to be unreachable, but got: {value}")
AssertionError: Expected code to be unreachable, but got: <Light.BLUE: 4>The lines between the first and the last File line are left out here. The paths in a traceback depend on the machine. The last line names the value that reached the case _: block, so the missing member is visible.
Some type checkers, which are tools that read code without running it, can report a match that misses a member when assert_never is used. This lesson does not run a type checker.
None values
Python's None means "no value at all". A match can have a pattern for it, written as case None:. Without that pattern, a None value does not stop a match. It simply does not equal any string, so it reaches _.
The next program adds the None pattern and runs three checks.
Output:
no input
starting
stopping
unknown: STOPThe None pattern runs before any text comparison. The STOP line shows the case-sensitive match again. "STOP" does not equal "stop", so it reaches _.
The error appears later, when a method is called on None. The next program calls .lower(), which is a method that works only on text.
Output:
Traceback (most recent call last):
...
return text.lower()
^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'lower'The message says that the value's type is NoneType, which is the type of None, and that it has no attribute called lower. An attribute is a name that belongs to a value, such as a method.
Common mistakes
Mistake 1: forgetting that matching is case-sensitive
The text "Start" does not match the pattern "start". The first example in this lesson shows this. If a user can type either case, change the text before the match, for example with text.lower(), and then compare with lowercase patterns.
match text.lower():
case "start":
...Mistake 2: passing None to a function that expects text
A match with a case _: never fails on None. The failure comes from the code inside a block, such as the .lower() call in the section above. The fix is a case None: pattern, or a check before the call.
Mistake 3: repeating a pattern
Python does not stop a repeated pattern. The first block that fits runs, and the later one never does. The next program has "start" twice.
Output:
startingNo error is shown. The second "start" block is dead code (code that can never run). Remove it, or merge the two patterns into one, such as case "start" | "begin":.
Mistake 4: using match as a value
A match is a statement. It cannot be written on the right side of =. The next program tries it.
s = "a"
n = match s:
case "a":
1
case "b":
2
print(n)Python refuses the file before it runs:
File "main.py", line 2
n = match s:
^
SyntaxError: invalid syntaxTo get a value, return it from a function in each block, as in the day_number example. For a small table of values, a dictionary works. A dictionary maps each key to a value and is written in braces, such as {"a": 1}. The .get method returns the value for a key, or a default when the key is missing.
Output:
1Mistake 5: a match that silently skips a value
The enum section shows a match that prints go only, because the call with YELLOW matched no pattern. Python does not warn about this. When every member must be handled, add a case _: that raises an error, such as the assert_never call above. When a value should be handled by doing nothing, write a case _: with pass (a statement that does nothing) and a comment that says so.
Mistake 6: converting text to an enum member that does not exist
Light["BLUE"] looks up a member by its name, and the name must match exactly. A name that is not in the enum raises an error. Calling the class with a value that is not in the enum raises a different error.
The second line uses "BLUE", which is not a member name. Its output is:
Light.GREEN
Traceback (most recent call last):
...
print(Light["BLUE"])
...
KeyError: 'BLUE'The lines between are left out because the paths depend on the machine. A call such as Light(9), which looks up by value rather than by name, raises ValueError: 9 is not a valid Light instead.
When the text comes from a user, check it first, or catch the error with try and except KeyError (try runs code that may fail, and except runs when it does), so the program can show a friendly reply.
Practice
Write each answer in a file, run it with python3 file.py, and compare the result with the answer.
- Write a function
kind_of(ext)that returnsimageforpng,jpg,giforimage, returnsdocumentforpdfortxt, and returnsotherfor anything else. Use amatchstatement.
Show the answer
def kind_of(ext):
match ext:
case "png" | "jpg" | "gif" | "image":
return "image"
case "pdf" | "txt":
return "document"
case _:
return "other"- An enum
SuitlistsHEARTS,DIAMONDS,CLUBSandSPADES. Write a functioncolor(suit)that returnsredfor hearts and diamonds andblackfor clubs and spades. Use amatchwith nocase _:block.
Show the answer
from enum import Enum, auto
class Suit(Enum):
HEARTS = auto()
DIAMONDS = auto()
CLUBS = auto()
SPADES = auto()
def color(suit):
match suit:
case Suit.HEARTS | Suit.DIAMONDS:
return "red"
case Suit.CLUBS | Suit.SPADES:
return "black"Every member is listed, so each suit gets a color. If one suit were left out of the match, the function would return None for it, and Python would not report the gap.
- The function below has a case for
"quit"only. What doesrun("quit")return, and what doesrun("Quit")return?
def run(cmd):
match cmd:
case "quit":
return "bye"
case _:
return "unknown"Show the answer
run("quit") returns bye. run("Quit") returns unknown, because the comparison is case-sensitive.
- This program does not run. Explain why, and fix it.
s = "a"
n = match s:
case "a":
1
case "b":
2
print(n)Show the answer
Python reports SyntaxError: invalid syntax on the line with match. A match is a statement, so it cannot be the value on the right side of =. One fix is a dictionary: n = {"a": 1, "b": 2}.get(s, 0). Another is a function that returns the value from each block.
- Change the
commandfunction from the None section so that aNoneinput returnsno input, and every other input behaves as before. Test it withNone, withstartand withstop.
Show the answer
def command(text):
match text:
case None:
return "no input"
case "start":
return "starting"
case "stop":
return "stopping"
case _:
return f"unknown: {text}"Run it with None, start and stop. The three results are no input, starting and stopping.
Summary
- A
matchstatement compares one value with a list of patterns, and runs the block of the first pattern that fits. Use_for the rest. - String matching is case-sensitive, so
"Start"and"start"are different values. - A bare name after
casecaptures any value. To compare with a fixed value, use a dotted name such asLight.RED. - Python does not check that a
matchon an enum covers every member. Useassert_neverin acase _:block to stop the program when a member is missing. - A
matchis a statement, not a value. ANonevalue does not stop amatch, but a method called onNonestops the program withAttributeError.