Need switch-case in Python? It's not match-case!

Share
Copied to clipboard.
Trey Hunner smiling in a t-shirt against a yellow wall
Trey Hunner
6 min. read 4 min. video Python 3.10—3.14

Folks migrating from another programming language into Python often look for a familiar switch-case statement, which Python does not have.

Python does have a match-case statement, but it's not the same thing as switch-case.

And usually, when you're looking for switch-case, I wouldn't recommend using match-case.

The power of match-case

Python's match statement is for structural pattern-matching, which sounds complicated because it is a bit complicated.

The match statement has a different way of parsing expressions within its case statements that's kind of an extension of the way that Python parses code in general.

And again, that sounds complex because it is.

I'll cover the full power of match-case another time, but let's quickly look at a few examples that demonstrate the power of match-case.

Matching iterables

Python's match statement can be used to match an iterable based on the number of items it contains (one, two, or three items, for example):

>>> point = (3, 4)
>>> match point:
...     case (x,):
...         print(f"1D point at {x}")
...     case (x, y):
...         print(f"2D point at ({x}, {y})")
...     case (x, y, z):
...         print(f"3D point at ({x}, {y}, {z})")
...
2D point at (3, 4)

The match statement can also match an iterable based on its actual values.

Here, we're matching the first value as being move, and the second one as being either up, down, left, or right. Or we're matching the first value as being turn:

>>> command = ["move", "up", 10]
>>> match command:
...     case ["move", "up" | "down" | "left" | "right" as direction, distance]:
...         print(f"Moving {direction} {distance} units")
...     case ["turn", direction]:
...         print(f"Turning {direction}")
...
Moving up 10 units

Pattern-matching with dictionaries

The match statement can also match a dictionary based on the keys that it contains.

Here, we're matching a dictionary with the key of type, the key of error, or something else:

>>> config = {"type": "rectangle", "width": 3, "height": 4}
>>> match config:
...     case {"type": type}:
...         print(f"Object is {type}")
...     case {"error": message}:
...         print(f"Error: {message}")
...     case _:
...         print(f"Unknown response")
...
Object is rectangle

We can also match a dictionary that has specific keys and specific values.

Here, we're looking for a type of circle, for example:

>>> config = {"type": "rectangle", "width": 3, "height": 4}
>>> match config:
...     case {"type": "circle", "radius": r}:
...         print(f"Circle with radius {r}")
...     case {"type": "rectangle", "width": w, "height": h}:
...         print(f"Rectangle {w}x{h}")
...
Rectangle 3x4

Matching objects by type and attributes

The match statement can also match an object based on its type:

>>> value = 4
>>> match value:
...     case int():
...         print(f"Integer: {value}")
...     case str():
...         print(f"String: {value}")
...     case list():
...         print(f"List with {len(value)} items")
...     case _:
...         print("Unknown type")
...
Integer: 4

It can also match objects based on the presence of certain attributes, or specific attributes having specific values:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

def describe_point(point):
    match point:
        case Point(x=0, y=0):
            return "Point at the origin"
        case Point(x=0, y=y):
            return f"On Y-axis at {y}"
        case Point(x=x, y=0):
            return f"On X-axis at {x}"
        case Point(x=x, y=y):
            return f"Point at ({x}, {y})"

For example, this match-case statement will match the Point(0, 0) as at the origin, but Point(0, 1) as on the y-axis at position 1:

>>> describe_point(Point(0, 0))
'Point at the origin'
>>> describe_point(Point(0, 1))
'On Y-axis at 1'

Nested pattern-matching

The various forms of structural pattern-matching can also be deeply nested.

Here, we're using a match-case statement with Python's ast module to parse Python code:

>>> find_sorted_assignments("x = sorted(y)")
Found: x assigned to sorted(y, ...)

This match-case statement is matching an Assign node, which has a Call node within it. That Call node is calling the [sorted][] function, while grabbing the name of the first argument passed to sorted, and grabbing the name of the variable that we're assigning to:

import ast

def find_sorted_assignments(python_code):
    for node in ast.parse(python_code).body:
        match node:
            case ast.Assign(
                targets=[target],
                value=ast.Call(
                    func=ast.Name(id="sorted"),
                    args=[iterable],
                ),
            ):
                target = ast.unparse(target)
                iterable = ast.unparse(iterable)
                print(f"Found: {target} assigned to sorted({iterable}, ...)")

That's a pretty complex match-case statement!

The match statement was designed to handle some pretty sophisticated pattern-matching. It really shines when implementing parsers like this.

match is not designed to be an equivalent to switch.

But why should you not use match as an equivalent to the switch statement?

Using if-elif instead of switch-case

In Python, if-elif blocks are pretty readable.

Compare this match-case block...

def get_day_type(day):
    match day.lower():
        case "monday" | "tuesday" | "wednesday" | "thursday" | "friday":
            return "weekday"
        case "saturday" | "sunday":
            return "weekend"
        case _:
            return "invalid"

... to this if-elif block:

def get_day_type(day):
    day_lower = day.lower()
    if day_lower in ("monday", "tuesday", "wednesday", "thursday", "friday"):
        return "weekday"
    elif day_lower in ("saturday", "sunday"):
        return "weekend"
    else:
        return "invalid"

I don't find the match block all that much easier to understand than the if block.

Also, unlike match-case, the if statement doesn't require an extra level of indentation.

And everyone probably understands the if-elif code. But not everyone necessarily understands the match-case code.

It is nice to avoid long if-elif statements. But replacing them with match-case doesn't help much.

Using a dictionary instead of switch-case

Sometimes you can replace long if statements with dictionaries.

Here's some code that converts HTTP status codes to their descriptions:

def get_status_message(code):
    if code == 200:
        return "OK"
    elif code == 201:
        return "Created"
    elif code == 400:
        return "Bad Request"
    elif code == 401:
        return "Unauthorized"
    elif code == 404:
        return "Not Found"
    elif code == 500:
        return "Internal Server Error"
    else:
        return "Unknown Status"

Note that we're essentially converting our input values to our output values. Each valid input value maps to a possible output value.

Python has a data structure for mapping data just like this... it's called a dictionary.

Here's the same code that uses a dictionary instead of a long if-elif chain:

STATUS_MESSAGES = {
    200: "OK",
    201: "Created",
    400: "Bad Request",
    401: "Unauthorized",
    404: "Not Found",
    500: "Internal Server Error"
}

def get_status_message(code):
    return STATUS_MESSAGES.get(
        code,
        "Unknown Status",
    )

I find this much more readable. I like that we've moved our key-value mappings into a data structure.

The code from before looked like a lot of logic, but it was really data disguised as logic.

The dictionary makes it clearer that this bit of code is data-heavy, but not actually very logic-heavy.

Keep match-case for advanced uses

Most Python programmers are not very familiar with match-case. It's not used very often.

And when it is used, it's typically used for pattern-matching, not for matching a single value.

The next time you find yourself tempted to use match-case, ask yourself whether a dictionary might be better instead.

A Python Tip Every Week

Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.