Python is one of the most trending languages, which sees growing job demands due to its prevalence in data science and artificial intelligence.
To keep up with the evolution of other emerging languages, such as Go, Julia, and Rust, the Python Software Foundation has been investing considerable resources to continuously make Python better.
Recently, it released a major version upgrade that marks Python 3.10. Don’t be fooled that it’s the last major release of Python 3, as 3.11 is being considered now, as indicated at Python’s official website.
Anyhow, Python 3.10 represents a major upgrade by providing several new features compared to 3.9, such as explicit type aliases, better annotations with the support of union operation, and of course, structural pattern matching — the topic of the present article.
A number of PEPs, including 634, 635, and 636, are devoted to the discussion of this new feature. So interested readers can have a full picture of the development of this feature from the official perspective. Here, I want to simply provide an overview of this technique and highlight some of the most common use cases.
For easier reference, I’ll take the bold move to refer to structural pattern matching as SPM, which is the acronym of this technique. Please note that as far as I know, I’ve not seen such a name in the official documentation. It’s just easy for me in my writing.
Note: Python 3.10 has been officially released, and you can find it on Python’s official site. Here’s a link to the download page.
Overview and The Basic Syntax
Before we go any further, let’s first understand what structural pattern matching is through a hypothetical scenario.
Suppose that we receive a response from an API call, which resumes a tuple object with the first item being the status code and the second item is the data, contingent upon whether the request is successful or not.
In other words, when the request is failed, the tuple contains only one item. The following shows you a possible solution:
As you can see, the solution works per our business requirements. However, when our requirements evolve, we will have to create additional branches with multiple layers of nested if…elif…else… statements. When that happens, the readability and thus maintainability of our codebase drops significantly.
This is when SPM comes to play, which is designed to handle this business elegantly. This new feature that is added to 3.10 involves the use of two new keywords: match and case (strictly speaking, they’re known as soft keywords, see here for a discussion).
Let’s see how we can use SPM to implement this need. A possible solution is shown below:
As you can see, the overall readability of SPM-based implementation is much improved. It may remind many of the classic switch-case feature that is available in many programming languages. However, SPM is different — it’s better, let’s explore it next.
Match Multiple Values
In the previous code snippet, you’ve seen that we use a number of case clauses to create multiple patterns that resp can be checked against.
However, SPM is flexible in that each pattern can contain multiple values instead of just one. The following code snippet shows you a possible use case.
match resp:
case (200, data) | (100, data):
...
case ((404 | 401), data):
...
case ((404 | 401) as status_code, data):
...
You can aggregate different patterns into a single case clause. Using this, you’re simply testing multiple values. These alternative patterns or values are separated by the union operator (the vertical bar).
Alternatively, within each pattern, you can specify the possible values using the union operation.
To add additional flexibility, we can cast the possible values to a variable: (404 | 401) as status_code, such that we can use status_code in a later step.
In a more general term, the variables that are shown in the patterns can be referred to as captures. In essence, the value that matches the pattern is assigned to the corresponding variables in the pattern, such that these captured values can be used for further processing.
Apply Conditions
Besides matching multiple patterns and values, you can apply additional conditions to the case clause. That way, you can check the pattern on the fly without changing the pattern.
special_codes = [300, 301, 302]
match resp:
case (code, data) ifcode in special_codes:
...
To apply a condition, you simply add an if statement following the pattern. Please note that the variables used in the pattern are immediately available in the if statement.
Starred Expression and Wildcard
So far, we have been dealing with no more than two items in the tuple. However, it’s possible that you may get a variable number of items in the tuple or other kinds of data containers.
In these cases, you can consider using a starred expression, which will capture multiple items into a list for later processing, something like below.
Another useful feature that we’ve been used without mentioning is the wildcard pattern. Specifically, we use an underscore to denote anything that hasn’t been matched with previous patterns. This is kind of like the default clause in the switch-case statement in other languages.
match something
case pattern0:
...
case pattern1:
...
case pattern2:
...
case _:
# this is the wildcard pattern
Match Complicated Objects
The patterns we’ve been used are pretty simple: strings, integers, and tuples. The power of SPM is that it can match complicated patterns that involve seemingly complicated objects, such as dictionaries and custom instances.
match resp_dict:
case {"code": status_code, "data": data}:
print("Status Code:", status_code)
print("Data:", data)
As shown above, we can create a pattern that is a dict object, and the value needs to match the keys used in the dictionary.
In a more advanced scenario, we can even check the value against a custom instance-based pattern, like below.
As shown above, we check the person against a few patterns involving the custom classes. One important thing is that you’ll have to use keyword parameters to specify the pattern.
Conclusion
In this article, we reviewed the most useful features of the new feature in Python 3.10 — the Structural Pattern Matching. As you can see, it’s a powerful feature that is going beyond the classic switch-case usage in other programming languages, which mostly require exact matching. Python takes a step further by supporting defining a general pattern that significantly improves the flexibility of this technique.
Whenever you need to deal with multiple possible scenarios, it’s maybe the best time to consider using structural pattern matching, which has much better readability than multiple if…elif…else statements.