Structural Pattern Matching in Python: A Comprehensive Guide
Structural pattern matching is a feature added in Python 3.10 that makes it easier to work with different types of data and control the flow of your code.
Instead of writing long chains of if, elif, and else statements, you can write cleaner and easier-to-read code using pattern matching. It works with many data types—like lists, dictionaries, and even custom classes—so it’s helpful in many situations.
This article teaches you how to use pattern matching in your Python projects. We’ll walk through the basics, show typical use cases, and share tips for writing clean, effective code.
Prerequisites
Before you start, make sure you're using Python 3.13 or later. You can check your current version by running:
If you're using an older version, you'll need to download and install a newer one to use structural pattern matching.
Getting started with pattern matching
To get a feel for how pattern matching works, let’s start with a simple example and compare it to the traditional way of writing control flow in Python.
First, create a new folder for your project and move into it:
Now, create a file called app.py. This is where you’ll write your code.
Here’s how a basic command processor would look using the if-elif structure, without pattern matching:
The process_command() function checks the value of command using a series of if-elif conditions. Each condition returns a response based on the command. A fallback message is returned if the command doesn't match any known options.
To run the file, enter the following in your terminal:
The output should be:
This works fine for simple cases, but as the number of commands grows or the logic becomes more complex, the if-elif chain becomes harder to read and maintain.
With pattern matching, you get a cleaner and more structured way to handle multiple conditions:
In this version, the structure is much clearer:
- The
matchstatement checks the value ofcommand. - Each
casehandles a specific match and runs the corresponding code. case _:is a wildcard that catches anything that doesn’t match the earlier cases—similar to adefaultclause in a traditional switch statement.
While this example is simple, pattern matching shines when working with more complex data and nested structures, as you'll see in the next sections.
When you run the file, the output will be:
The result is the same, but the code is easier to read and extend.
Pattern matching with different types
Now that you understand the basics, let's see how pattern matching handles different data types. This is where pattern matching really shines compared to traditional if-else chains.
Let's modify our app.py file to see how pattern matching can elegantly handle different types of inputs:
In this code, the describe_data() function uses pattern matching to respond differently based on the input type. Each case matches a specific data type:
Nonematches a missing or undefined value.int(n)captures an integer and binds it ton.str(s)handles strings and gives their length.list(l)anddict(d)handle lists and dictionaries, reporting how many items or keys they have.case _:is the fallback for anything that doesn't match the earlier patterns (like a float, in this case).
To see it in action, run the file with:
You'll see:
Without pattern matching, you'd need to write a series of isinstance() checks to achieve the same result:
The pattern-matching version is more concise and follows a consistent structure that clearly separates the type checking from the value processing.
Adding conditions with guard clauses
You can make your patterns more precise by adding conditions using guard clauses with the if keyword.
To try this out, replace the contents of app.py with the following:
This example uses guard clauses in pattern matching to handle numbers more precisely. Conditions like int(x) if x < 0 match negative integers, while others handle zero, even and odd integers, or floats that represent whole numbers.
The if keyword adds extra checks to each pattern, making the logic more specific and readable. A final fallback catches anything that doesn’t match, like strings. This approach keeps the code clean and easy to follow, even with varied input.
Running this will show:
This approach keeps related logic grouped together in a clear, readable format that's easy to understand and maintain.
Pattern matching with sequences
Pattern matching shines when working with sequences like lists and tuples. Let's see how it can help us extract and process data from these structures.
Replace the contents in your app.py file with this example:
The analyze_point() function identifies different types of points on a coordinate system:
- Points at the origin (0, 0)
- Points on the x-axis or y-axis
- Points on the diagonal where x equals y
- Any other valid point with x and y coordinates
When you run this code, you'll see:
Notice how pattern matching makes it easy to check for specific values in a tuple (like (0, 0)), capture variables from specific positions (like (0, y)), and even combine patterns with conditions (like (x, y) if x == y).
Working with lists
Lists are another sequence type where pattern matching is beneficial. Let's see how we can analyze lists of different lengths and structures:
The describe_list() function handles different list shapes. It detects empty lists, single-item and two-item lists, and lists with three specific elements.
It also uses rest patterns like *rest to match longer lists, and head-tail patterns like [first, *middle, last] to access the first and last items directly.
A guard clause ensures that one of the patterns only matches exactly three items.
Without pattern matching, this would require multiple length checks and indexing, making the logic more verbose and harder to follow
Running this code produces:
Pattern matching brings the focus back to the structure of your data, making your code more declarative and easier to understand.
Pattern matching with dictionaries
Dictionaries are a fundamental data structure in Python, and pattern matching makes it much easier to work with them, especially when they have nested or complex structures.
Let's explore how pattern matching handles dictionaries. Replace the contents in your app.py file:
The process_user() function demonstrates how pattern matching with dictionaries allows you to check for specific keys, automatically bind values to variables like name and age, and apply additional conditions using guard clauses (e.g. if age < 18).
Since patterns are matched in order, the most specific cases should come first to ensure the correct match is applied. When you run the code, the output will be:
When you run this code, you'll see:
The same logic without pattern matching would be significantly more verbose and harder to read:
This approach requires explicit key checking and deep nesting, making it harder to understand the different cases at a glance.
Final thoughts
Pattern matching brings clarity and structure to Python code, making it easier to handle complex conditions without relying on lengthy if-elif chains. It improves readability, simplifies value extraction, and handles different data shapes—like basic types and dictionaries—more elegantly.
For a deeper dive, check out PEP 634 or the official Python documentation.