Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

Branch out with pattern matching

Batteries included is a blog series about the Python Standard Library. Each day, I share insights, ideas and examples for different parts of the library. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.

When I started learning Rust, pattern matching became one of my favorite features right away. When pattern matching was announced for Python 3.10, I was more excited than I’ve been about any other new programming language feature.

One use case I really like the pattern matching syntax for is when deciding actions based on the structure of a JSON returned from an API.

A simplified example that checks for success status of 200 and passes the data for success function, checks for 404 and handles that separately and then handles everything else as default error:

def process(response):
  match response:
    case { 'status': 200, data: data }:
      return parse_success(data)
    case { 'status': 404 }:
      return parse_not_found(response)
    case _:
      return parse_error(response)

What I really like with Python’s pattern matching is that you can:

  1. Only check for the parts that you care for instead of having to model the entire response object
  2. Combine checking and capturing data to variables

This becomes especially handy when you’re not in charge of the API you’re calling and the return types are not very consistent. It enables you to write branching logic where you only have to care about the happy path and deal with all the other paths in the pattern matching.

Another example could be a recipe API that returns differently structured objects based on what type of meal you’re creating.

You can pick the properties you’re interested in and ignore everything else. Or like in the salad example below, you can pass everything else to a further function as a keyword argument dictionary.

def bake(recipe):
    match recipe:
        case {"type": "pizza", "toppings": toppings, "size": size}:
            print(f'Baking a pizza with {', '.join(toppings)} for {size} people')
        case {"type": "cake", "filling": filling}:
            print(f'Baking a cake with {', '.join(filling)}')
        case {"type": "salad", **others}:
            print(f"Salad with {others}")


bake({"type": "pizza", "toppings": ["cheese", "salami"], "size": 8})
# Baking a pizza with cheese, salami for 8 people
bake({"type": "cake", "filling": ["cream", "strawberry jam"]})
# Baking a cake with cream, strawberry jam
bake({"type": "salad", "dressing": "French dressing", "vegan": True})
# Salad with {'dressing': 'French dressing', 'vegan': True}