How to use match-case statement in Python

Jul 14, 2024#python

The match-case statement in Python is conceptually similar to the switch-case statement found in many other programming languages. Both are used to control the flow of a program by comparing a single expression against multiple possible patterns or values and executing different blocks of code based on which pattern or value matches.

Syntax

match subject:
    case pattern if condition:
        # code to execute if pattern matches and condition is True
    case pattern1:
        # code to execute if pattern1 matches
    case pattern2:
        # code to execute if pattern2 matches
    ...
    case _:
        # code to execute if no other pattern matches
  • Each case specifies a pattern to match against the value provided to match. Patterns can be literal values (like integers, strings), variable names, sequences, mappings, custom classes, and can include additional conditions (guards).
  • A guard is an if condition appended to a case pattern. The code block for that case is executed only if the pattern matches and the guard condition evaluates to True.
  • Unlike switch-case in some languages, match-case does not fall through to subsequent cases. Once a match is found and its block is executed, the match statement is exited.
  • You can use the | operator to match multiple literals in a single case.
  • You can use _ as a wildcard to handle any value not explicitly matched by previous cases.

Matching Patterns

  1. Matching against literals, a straightforward way to compare a value directly against specific, fixed values (literals). Literals can include integers, strings, booleans, and other constant values.
def describe_status_code(code):
    match code:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return "Unknown status code"

def greet(name):
    match name:
        case "Alice":
            return "Hello, Alice!"
        case "Bob":
            return "Hello, Bob!"
        case _:
            return "Hello, stranger!"

def classify_number(number):
    match number:
        case 0 | 1 | 2:
            print("Small number")
        case 3 | 4 | 5:
            print("Medium number")
        case _:
            print("Large number")
  1. Matching against variables, the variable gets bound to the value that matches the pattern. You can combine variable binding with guards to match more specific cases.
def describe_number(num):
    match num:
        case x:
            return f"The number is {x}"

def process_value(value):
    match value:
        case x if x > 0:
            return f"Positive value: {x}"
        case x if x < 0:
            return f"Negative value: {x}"
        case 0:
            return "Zero"
  1. Matching against sequences, allows you to decompose and work with lists, tuples, and other iterable data structures directly within your pattern matching logic, powerful for handling structured data in a clear and readable way.
def process_sequence(seq):
    match seq:
        case [x, y]:
            return f"Sequence of two elements: {x}, {y}"
        case [x, y, z]:
            return f"Sequence of three elements: {x}, {y}, {z}"
        case _:
            return "Other sequence"

def process_tuple(tpl):
    match tpl:
        case (a, b):
            return f"Two-element tuple: {a}, {b}"
        case (a, b, c):
            return f"Three-element tuple: {a}, {b}, {c}"
        case _:
            return "Other tuple"
  1. Matching against mappings, allows you to decompose and work with dictionaries directly within your pattern matching logic, particularly useful for handling structured data that is stored in key-value pairs.
def describe_person(person):
    match person:
        case {"name": name, "age": age}:
            return f"Name: {name}, Age: {age}"
        case _:
            return "Unknown person"
  1. Matching against class instances, allows you to decompose and work with object attributes directly within your pattern matching logic. By default, a class does not support positional sub-patterns in pattern matching, you need to use the __match_args__ attribute in the class definition, which is a tuple that specifies which attributes should be considered positional for the purposes of pattern matching.
class Point:
    __match_args__ = ("x", "y")

    def __init__(self, x, y):
        self.x = x
        self.y = y

def describe_point(point):
    match point:
        case Point(x, y):
            return f"Point with coordinates: ({x}, {y})"
        case _:
            return "Unknown point"


p = Point(1, 2)
print(describe_point(p))  
# Output: Point with coordinates: (1, 2)
  1. Matching against enums, a structured way to define symbolic names that represent constant values, which can be used effectively in pattern matching.
from enum import Enum

class TrafficLight(Enum):
    RED = "Stop"
    YELLOW = "Proceed with caution"
    GREEN = "Go"

def describe_traffic_light(color):
    match color:
        case TrafficLight.RED:
            return TrafficLight.RED.value
        case TrafficLight.YELLOW:
            return TrafficLight.YELLOW.value
        case TrafficLight.GREEN:
            return TrafficLight.GREEN.value
        case _:
            return "Unknown traffic light"