Match Statement

status = 500
match status:
    case 404:
        print("Not found")
    case 418:
        print("I'm a teapot")
    # Wildcard case
    case _:
        print("Some other status")

Creating a case such as case var: is not allowed, even when var is defined above. Python binds the matched value to var and it would always match.

Match statements do not fall through.

Or Patterns

match 1:
    case 2 | 3:
        print("2 or 3")

match [1, 2]:
    case [1, 2 | 3]:
        print("1, 2 or 3")

This is allowed with all types as long as each case binds the same variables, for example [1, x] | [x, 2]. Cases in the pattern are evaluated left-to-right.

Match Guards

msg = "OK"
status = 0
match msg:
    case "OK" if status == 1:
        print("Good response")
    case _ if status == 0:
        print("Bad time")
    case _:
        pass
# Output: Bad time

The if statement is only evaluated if the pattern matches initially and after all the pattern variables have been bound.

Using The Matched Value

match ("go", "south"):
    case ("go", "north" | "south" as direction):
        print(f"You head {direction}")

The as keyword can be used to bind any otherwise unnamed value in the case.

Lists & Tuples

m = 4
match [3, 4, 5]:
    # `x` and `y` are bound here
    case [x, y]:
        print(f"Any two-element list {x}, {y}")
    # `m` is from the outer scope
    case [m, 4]:
        print(f"Two-element list 4, 4")
    case [3, *rest]:
        print("3", rest)
    case _: # Equivalent to [*items]
        pass
# Output: 3, [4, 5]

This always matches the exact number of list elements. This is the same for Tuples.

Dictionaries

match {"name": "Bob", "age": 32}:
    # `n` is bound here
    case {"name": "James", "age": n}:
        print(n)
    case {"name": "Bob"}:
        print("It's Bob.")

Unlike the list example, dictionary cases match when the set of keys in the value is a superset of the case. For example, the second case in the above example will match even though the value has an extra key: "age".

Objects

class Ok:
    __match_args__ = ("value",)

    def __init__(self, value):
        self.value = value

class Err:
    def __init__(self, err_msg):
        self.err_msg = err_msg

match Ok(5):
    case Ok(n):
        print(f"Value: {n}")
    case Err(err_msg=msg): # Without __match_args__
        print(f"Error: {msg}")

Builtin Types

var = 1
match var:
    case str():
        print("String")
    case bool():
        print("Boolean")
    case int():
        print("Integer")

bool() must come before int() to distinguish them. Boolean values are coerced to integers, so otherwise the integer case would match before the boolean case. The cases do not create instances of the types.

from typing import Type
import builtins # Cannot use `from builtins import str`

type_var: Type = str
match type_var:
    case builtins.str:
        print("String")
    case builtins.int:
        print("Integer")