Python Match Case or Pattern: Real Syntax Guide
python match case or pattern: Learn Python's match case syntax with practical examples: literals, sequences, guards, class patterns, and when to prefer it over if/elif.
Python's match case (introduced in 3.10) is often described as a switch statement, but it is actually structural pattern matching. The match statement compares a subject against a series of patterns, and the first pattern that matches determines which block runs. This is more expressive than a simple value comparison because patterns can destructure data, bind variables, and apply guards. If you're searching for python match case or pattern, the key is to understand that you are not just replacing if/elif chains; you are gaining a tool for extracting structure from data.
What match case Actually Does
The match statement evaluates an expression and attempts to match it against one or more case patterns. Each case has a pattern and an optional guard. When a pattern matches, the corresponding block executes, and any variables bound in the pattern become available. If no pattern matches, nothing happens unless you include a wildcard case _.
command = "start" match command: case "start": print("Starting...") case "stop": print("Stopping...") case _: print("Unknown command")
This looks like a switch, but the real power appears when patterns include structure. The subject can be any Python object, and patterns can specify types, lengths, and even nested shapes.
Matching Literal Values
The simplest patterns are literals: numbers, strings, booleans, and None. They are compared using equality, so case 1 matches the integer 1, and case "red" matches the string "red". You can also combine literals with | to match multiple values in one case.
status = 404 match status: case 200 | 201: print("Success") case 404: print("Not Found") case _: print("Other")
Literal patterns are evaluated at runtime using ==, so they work with any object that implements equality. This is straightforward, but it does not yet justify the complexity of match over a simple dictionary lookup.
Sequence and Mapping Patterns
Sequence patterns match lists, tuples, and other iterables by their length and element structure. You can use *rest to capture a variable number of elements. Mapping patterns match dictionaries by keys, optionally binding values to variables.
point = (3, 4) match point: case (0, 0): print("Origin") case (x, 0): print(f"X-axis at {x}") case (0, y): print(f"Y-axis at {y}") case (x, y): print(f"Point at ({x}, {y})")
Here the pattern (x, 0) matches any two-element tuple whose second element is 0, and binds the first element to x. This is far more compact than manual unpacking and conditional checks.
Mapping patterns use {} and key names. You can require specific keys and bind their values:
data = {"name": "Alice", "age": 30} match data: case {"name": name, "age": age}: print(f"{name} is {age}") case {"name": name}: print(f"{name} has no age")
Note that mapping patterns do not require the dictionary to contain only those keys; extra keys are ignored. If you need to capture the whole dictionary, use **rest.
Using Guards and the Wildcard Pattern
A guard is an if condition attached to a case. The pattern must match first, then the guard is evaluated. If the guard is false, the case is skipped and matching continues. This is useful for constraints that cannot be expressed purely in the pattern.
value = 15 match value: case int(x) if x > 10: print(f"Large int: {x}") case int(x): print(f"Small int: {x}") case _: print("Not an int")
The wildcard _ matches anything and is typically used as the final fallback. It is not a variable; it does not bind. If you need to capture the value without caring about its structure, use a name like other instead.
Guards are evaluated only after the pattern succeeds. This means you can safely reference variables bound in the pattern without risking a NameError.
Matching Class Instances
Class patterns let you match objects based on their type and attribute values. The syntax uses the class name followed by parentheses, with positional or keyword arguments. For positional arguments, the class must have a __match_args__ attribute that defines which attributes map to positions.
class Point: __match_args__ = ("x", "y") def __init__(self, x, y): self.x = x self.y = y p = Point(2, 3) match p: case Point(0, 0): print("Origin") case Point(x, y): print(f"({x}, {y})")
Without __match_args__, you must use keyword patterns like Point(x=0, y=0). This is useful when matching dataclasses or namedtuples, which often define __match_args__ automatically. Class patterns perform an isinstance check before accessing attributes, so they are safe with subclasses.
Common Pitfalls in Pattern Matching
One frequent mistake is assuming that a name in a pattern is an existing variable to compare against. In pattern matching, a bare name always binds a new variable. To compare against an existing value, you must use a value pattern, which requires a dotted name or a literal. For example, case Color.RED: works, but case RED: binds a variable named RED.
Another pitfall is using | with patterns that bind different variables. Python requires that all alternatives in a single case bind the same set of names. case 1 | x: is invalid because x is not bound in the first alternative.
Also, sequence patterns are greedy with *rest. The *rest can appear only once per pattern and captures the remaining elements. Placing it in the middle is allowed, but it may cause unexpected behavior if you are not careful about the expected length.
Performance and Maintainability Tradeoffs
match is not a jump table like C's switch. It evaluates patterns sequentially, and each pattern may involve type checks, attribute access, and equality comparisons. For a small number of cases, this is negligible, but if you have dozens of complex patterns, an if/elif chain or a dictionary dispatch might be faster. However, the difference is rarely the bottleneck in real applications.
The bigger benefit is maintainability. Pattern matching often reduces the amount of nested conditional code and makes the structure of the data explicit. For example, parsing a command tuple becomes a single match with clear cases instead of multiple isinstance checks and unpacking logic. This improves readability when the data shape is the primary driver of control flow.
Use match when you need to destructure or type-check in a way that would otherwise require verbose boilerplate. Use plain if when the condition is a simple boolean expression or when you need to perform arbitrary comparisons that do not fit a pattern.
Version and Compatibility Requirements
match case is available only in Python 3.10 and later. If you are writing code that must run on Python 3.9 or earlier, you cannot use this syntax. For projects that need backward compatibility, consider using a library like matchpy or stick to if/elif chains. Also, be aware that some linters and type checkers may have limited support for pattern matching depending on their version.
When upgrading a codebase, you do not have to convert every if/elif chain to match. The new syntax is additive; you can adopt it in modules where structural matching genuinely simplifies the logic. This incremental approach avoids unnecessary churn and keeps the diff focused.