Python Structural Pattern Matching — Top 3 Use Cases To Get You Started

Python Structural Pattern Matching — Top 3 Use Cases To Get You Started

Python 3.10 added support for switch statements — Here are 3 practical examples to get you started

Python’s equivalent of a switch statement is finally here in Python 3.10. It hides behind a fancy name of Structural Pattern Matching, and does what you’d expect and more. It packs support for working with primitive data types, sequences, and even classes and enums.

Today I’ll show you how to get started, and debate in which cases you should use it. We’ll start by creating a new environment based on Python 3.10.

Don’t feel like reading? Watch my video instead:


How to Configure a Python 3.10 Environment

We’ll create a new virtual environment with Anaconda. If you’re using something else to manage your Python workflow, please search online for the equivalent commands.

Execute the following commands from the terminal line by line to create a new Python 3.10 virtual environment named py310, install and launch Jupyter Lab:

conda create — name py310 python=3.10
conda activate py310
conda install jupyter jupyterlab
jupyter lab

You can now use the following command to verify you’re using Python 3.10 (assuming you’re in Jupyter or a Python shell):

import platform
platform.python_version()

You should see 3.10.0 printed out, which means you’re good to go.

Python Structural Pattern Matching — Basic Syntax

Let’s cover the basic syntax behind structural pattern matching before diving into the use cases. The whole thing is based on two keywords:

  • match— The subject you want to evaluate conditions for. It could be a status code returned by an API request.
  • case— An individual condition evaluated to see if a match is confirmed.

Here’s the generic syntax of pattern matching from the official documentation:

match subject:
    case <pattern_1>:
        <action_1>
    case <pattern_2>:
        <action_2>
    case <pattern_3>:
        <action_3>
    case _:
        <action_wildcard>

As you can see, structural pattern matching in Python doesn’t require a break statement at the end of each case. The last case (_) will be used if an exact match wasn’t found on the other cases.

You now know the basics, so let’s dive into examples.

Python Structural Pattern Matching — Match Against Simple Values

Let’s start with a simple one. We’ll write a function called http_error() that accepts a status code. We’ll then use the status code as a subject of the match statement and write a couple of cases for different conditions:

def http_error(status):
    match status:
        case 200:
            return 'OK'
        case 400:
            return 'Bad request'
        case 401 | 403 | 404:
            return 'Not allowed'
        case _:
            return 'Something is wrong'

FYI, you can combine multiple conditions in a single pattern using |. For example, status codes 401, 403, and 404 return the same result, so it makes no sense to write three separate cases.

Let’s now call the http_error() function with different status codes to verify it works properly:

Image 1 — Basic structural pattern matching in Python (image by author)

Image 1 — Basic structural pattern matching in Python (image by author)

As you can see, the function won’t crash even if you pass in a string. Next, we’ll dive into more advanced use cases.

Python Structural Pattern Matching — Matching Complex Patterns

Sometimes matching on a single variable just won’t cut it. The good news is — you can throw almost anything imaginable inside Python’s match statement.

Take a look at the code snippet below — it declares a function called get_service_level() that accepts user data as a dictionary. The goal is to return a different service level, based on the user subscription type (free, premium), and type of the message (info, error). In the case of an info message, the service level is 0, regardless of the subscription type. In the case of a free user with an error message, the service level is 1. Finally, in the case of a premium user with an error message, the service level is 2:

def get_service_level(user_data: dict):
    match user_data:
        case {'subscription': _, 'msg_type': 'info'}:
            print('Service level = 0')
        case {'subscription': 'free', 'msg_type': 'error'}:
            print('Service level = 1')
        case {'subscription': 'premium', 'msg_type': 'error'}:
            print('Service level = 2')

It somehow feels unnatural to write a dictionary after a keyword, but that’s something you’ll have to get used to. Anyhow, here are the tests for all four possible scenarios:

Image 2 — Matching complex patterns in Python (image by author)

Image 2 — Matching complex patterns in Python (image by author)

You could further improve the function by adding a default case, but you already know how to do that.

Python Structural Pattern Matching — Matching Patterns in Classes

You can use structural pattern matching with Python’s classes and data classes. The documentation shows you how to work with data classes, so I’ll cover regular ones here. In a nutshell, you can use a class name followed by an argument list passed into a constructor as a case pattern.

The example below shows you how to implement the same service level logic in a Python class. Two parameters are passed to a constructor — subscription type and message type. For any info message, the level is 0. In the case of a free user with an error, the level is 1. Finally, in the case of a premium user with an error, the level is 2. We’ll also add a default case, just to mix things up:

class ServiceLevel:
    def __init__(self, subscription, msg_type):
        self.subscription = subscription
        self.msg_type = msg_type
        
    def get_service_level(user_data):
        match user_data:
            case ServiceLevel(subscription=_, msg_type='info'):
                print('Level = 0')
            case ServiceLevel(subscription='free', msg_type='error'):
                print('Level = 1')
            case ServiceLevel(subscription='premium', msg_type='error'):
                print('Level = 2')
            case _:
                print('Provide valid parameters')

The syntax is different from what we had in the second example but is still understandable. I’d argue it’s also easier to read. Imagine you had more than two parameters — passing them as dictionaries would get messy real fast.

Let’s test it for the four possible scenarios and a default case:

Image 3 — Structural pattern matching in Python classes (image by author)

Image 3 — Structural pattern matching in Python classes (image by author)

Works like a charm. Let’s stop with practical examples here and shift focus on one important question — when should you use structural pattern matching?

Final Thoughts

A dedicated switch statement has been long overdue in Python. Structural pattern matching packs everything switch statement does, and much more. It’s a new way to write and evaluate conditions. But as Corey Taylor once said —new does not mean best.

There’s no point in bothering with structural pattern matching if all you’re doing is testing simple conditions. Also, structural pattern matching is only available in Python 3.10. You’ll get an error if you try to use older Python releases. Is it worth it to upgrade?

Use structural pattern matching primarily when you have more complex patterns to evaluate, like the ones covered today.

What do you guys think of structural pattern matching? Have you replaced some of your old code with it, and why? Please let me know in the comment section below.


Stay connected