Pattern matching is certainly the most interesting new feature in the new Python 3.10 release, and in this tutorial you will learn everything about it!
It is pretty simple to understand, but it also comes with a lot of different code syntax options you can use. Now we are going to have a look at all of them. We start with the basic syntax, and then move on to all the different details.
Reference: A lot of examples are taken from the well written official documentation: What’s New In Python 3.10
1 Basic Syntax
The basic syntax is pretty easy. You use the match keyword and the case keyword and can then match a variable against different values. For each matched case you can then perform a certain action.
In the simplest case like here we match against simple values, but you can also use complex patterns. You will see what I mean in a few moments.
Syntax:
match subject:
case <pattern_1>:
<action_1>
case <pattern_2>:
<action_2>
case _:
<action_wildcard>
Example:
def http_error(status):
match status:
case 400:
return "Bad request"
case 401 | 403:
return "Not allowed"
case 404:
return "Not found"
case 418:
return "I'm a teapot"
case _:
return "Something's wrong with the internet"
2 Multiple Matches
We can match against multiple values in one line using the pipe operator
def http_error(status):
match status:
case 400:
return "Bad request"
case 401 | 403 | 404:
return "Not allowed"
3 No Fall Through
Compared to other programming languages it is important to note that here we don’t fall through all cases. For example the value 400 here prints only Bad request. In other languages like C++ this would then fall through all cases and also print all other statements, unless we use a break statement. But we don’t need this here in Python.
def http_error(status):
match status:
case 400:
print("Bad request")
case 401 | 403 | 404:
print("Not allowed")
4 Wildcard
A single underscore is used as the wildcard. This means if none of the above patterns matches, the wildcard action is performed. This is optional, so you can also omit it, and then nothing at all happens if no match is found.
def http_error(status):
match status:
case 400:
return "Bad request"
case _:
return "Something's wrong with the internet"
5 Unpacking and Variable Binding
Patterns can look like unpacking assignments, and a pattern may be used to bind variables. In this example we match a tuple here, and the data point can be unpacked to its x- and y-coordinate. We can then use the bound variable in the execution statement.
# point is an (x, y) tuple
match point:
case (0, 0):
print("Origin")
case (0, y):
print(f"Y={y}")
case (x, 0):
print(f"X={x}")
case (x, y):
print(f"X={x}, Y={y}")
case _:
raise ValueError("Not a point")
6 Patterns and Classes
If you are using classes to structure your data, you can use as a pattern the class name followed by an argument list that matches the arguments from the constructor. This pattern has the ability to capture class attributes into variables.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def location(point):
match point:
case Point(x=0, y=0):
print("Origin is the point's location.")
case Point(x=0, y=y):
print(f"Y={y} and the point is on the y-axis.")
case Point(x=x, y=0):
print(f"X={x} and the point is on the x-axis.")
case Point():
print("The point is located somewhere else on the plane.")
case _:
print("Not a point")
7 Patterns With Positional Parameters
We can use positional arguments if for example we use a builtin data class that provides an ordering for the attributes. Then all these patterns with positional arguments or keyword arguments are equivalent.
Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)
8 Nested Patterns
Patterns can be nested. For example, if our data is a short list of points, it could be matched like this. Here we match against different possible lists.
points = [Point(0, 0), Point(1, 1)]
match points:
case []:
print("No points in the list.")
case [Point(0, 0)]:
print("The origin is the only point in the list.")
case [Point(x, y)]:
print(f"A single point {x}, {y} is in the list.")
case [Point(0, y1), Point(0, y2)]:
print(f"Two points on the Y axis at {y1}, {y2} are in the list.")
case _:
print("Something else is found in the list.")
9 Complex Patterns and the Wildcard
A wildcard can not only be used as last case statement, but also in more complex patterns such as tuples. In this example the status variable matches against all tuples with an error, a code, and then any arbitrary status number.
status = ('error', 'Client Error', 100)
match status:
case ('warning', code, 10):
print("A warning has been received.")
case ('error', code, _):
print(f"An error {code} occurred.")
10 Guard
We can add an if clause to a pattern, known as a “guard”. And if the guard is false, the match goes on to try the next case block.
match point:
case Point(x, y) if x == y:
print(f"The point is located on the diagonal Y=X at {x}.")
case Point(x, y):
print(f"Point is not on the diagonal.")
11 More on Sequences
Sequence patterns also support wildcards: This star rest in the tuple or list works similar to wildcards in unpacking assignments. So these examples match a sequence of at least two items, and then binds all the remaining items to the rest. The name after the star may also be an underscore if you don’t need to bind the the remaining items.
[x, y, *rest]
(x, y, *rest)
(x, y, *_)
12 Mapping Patterns
Similar to sequences, we can also match mappings. In this example it captures the „bandwidth“ and „latency“ values from a dictionary. But here unlike sequence patterns, extra keys are ignored. A wildcard with double asterisks is also supported, but double asterisks and the underscore would be redundant, so this is not allowed.
{"bandwidth": b, "latency": l}
{"bandwidth": b, **rest}
# {"bandwidth": b, **_} Not allowed!
13 Match with Enum
Of course we can also match agains enums using the full doted name:
from enum import Enum
class Color(Enum):
RED = 0
GREEN = 1
BLUE = 2
match color:
case Color.RED:
print("I see red!")
case Color.GREEN:
print("Grass is green")
case Color.BLUE:
print("I'm feeling the blues :(")
14 Bind Subpatterns with as
And the last feature I show you is that subpatterns can be captured using the as keyword:
In this example it binds x1, y1, x2, y2 like you would expect without the as clause. And it also binds p2 to the entire second item.
points = (Point(0, 0), Point(1, 1))
match points:
case (Point(x1, y1), Point(x2, y2) as p2):
print(p2)
# --> Point(x=1, y=1)
Final Note
I hope you enjoyed this article and learned something new. Just one more word of warning. I personally really like this feature, but of course don’t overuse it. In many examples - like in this one - a simple if else statement will do just fine.
var = True
match var:
case True:
print('condition is True')
case False:
print('condition is True')
# simple if else is just fine
if var:
print('condition is True')
else:
print('condition is True')
If the article was helpful, make sure to share it on Twitter and let me know if you like this new feature!