Match-case: мощная замена if-elif-else

Раздел: Python -> Управляющие конструкции

Что такое match-case и зачем он нужен

Конструкция match (появилась в Python 3.10) реализует сопоставление с образцом (pattern matching). Она позволяет заменять длинные цепочки if-elif-else более читаемым и выразительным кодом, особенно при работе со сложными структурами данных. Основное преимущество - декларативное описание того, каким шаблонам должно соответствовать значение.

Базовый синтаксис:

match значение:
    case шаблон1:
        # действия
    case шаблон2:
        # действия
    case _:
        # действия по умолчанию

оператор case в python (оператор case (match) в python)

Ключевое слово match принимает объект, а case - шаблон, с которым сравнивается объект. Переменные, указанные в шаблоне, получают значения из соответствующих частей объекта. Символ _ (подчёркивание) действует как универсальный шаблон, совпадающий с любым значением. Важно: порядок case имеет значение - первое совпадение выполняется, остальные игнорируются.

Пример простейшего использования:

x = 2
match x:
    case 1:
        print("один")
    case 2:
        print("два")
    case _:
        print("другое число")

Match case python (конструкция match-case в python)

два

операторы ветвления в языке python (условные операторы в python)

Если ни один шаблон не совпал, выполняется case _. Без такого универсального шаблона программа вызовет ошибку, если ни один case не подошёл.

Как сопоставить значение с конкретным литералом?

Литералы (числа, строки, булевы значения) указываются непосредственно в case. Это самый простой вариант, заменяющий if x == 1: ... elif x == 2: ....

response = 404
match response:
    case 200:
        print("Успех")
    case 404:
        print("Не найдено")
    case 500:
        print("Ошибка сервера")

Return s s python (оператор return в python)

Не найдено

оператор выбора в python (оператор выбора if в python)

Типичные ошибки:

  • Попытка использовать переменную в качестве литерала: case code: - это не литерал, а захват переменной, который сработает всегда. Для сравнения с заранее известным значением используйте guard: case _ if code == expected:.
  • Забытый универсальный шаблон: при отсутствии case _ и несовпадении всех шаблонов возникает ошибка MatchError.

Как сопоставить с последовательностью (списком, кортежем)?

Шаблон последовательности позволяет извлечь элементы в переменные. Количество элементов может быть фиксированным или переменным.

point = (3, 7)
match point:
    case (0, 0):
        print("Начало координат")
    case (x, 0):
        print(f"На оси X: {x}")
    case (0, y):
        print(f"На оси Y: {y}")
    case (x, y):
        print(f"Точка ({x}, {y})")

Python пустая команда (пустая команда pass в python)

Точка (3, 7)

как на языке python записывается полное ветвление (полное ветвление в python)

Для переменной длины используйте *args:

items = [1, 2, 3, 4]
match items:
    case [first, second, *rest]:
        print(f"Первый: {first}, второй: {second}, остальные: {rest}")

циклы в python примеры (примеры циклов в python)

Первый: 1, второй: 2, остальные: [3, 4]

программа с циклом while python (программа с циклом while на python)

Типичные ошибки:

  • Путаница между кортежем и списком: шаблон [a, b] не совпадет с кортежем (1, 2) и наоборот. Python воспринимает квадратные и круглые скобки как разные шаблоны.
  • Попытка присвоить один и тот же элемент разным переменным: case [a, a]: - ошибка компиляции, имена в одном шаблоне должны быть уникальны.

Как сопоставить с объектом класса или именованными атрибутами?

Шаблон класса позволяет проверить тип объекта и извлечь его атрибуты. Структура: case ИмяКласса(атрибут1=переменная, атрибут2=переменная).

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

p = Point(5, 10)
match p:
    case Point(x=0, y=0):
        print("Начало")
    case Point(x=x, y=y):
        print(f"Точка: x={x}, y={y}")

For и while python в чем разница (разница между циклами for и while в python)

Точка: x=5, y=10

Python if then else (условный оператор if then else в python)

Типичные ошибки:

  • Необходимо, чтобы у класса был явно определён метод __match_args__, если атрибуты передаются позиционно. В противном случае используйте только именованные аргументы.
  • Шаблон сработает, только если объект является экземпляром именно этого класса (не подкласса). Для подклассов потребуется другой шаблон.

Как добавить дополнительное условие в шаблон (guard)?

С помощью ключевого слова if после шаблона можно задать дополнительное булево условие. Шаблон совпадёт только если и сам шаблон подошёл, и условие истинно.

value = 42
match value:
    case x if x % 2 == 0:
        print(f"Чётное число {x}")
    case x:
        print(f"Нечётное число {x}")

условный оператор в языке программирования python (условный оператор в python)

Чётное число 42

Python list цикл (цикл по списку в python)

Guard позволяет сравнивать с внешними переменными или динамически вычислять условия.

expected_code = 404
http_code = 404
match http_code:
    case code if code == expected_code:
        print("Код совпадает с ожидаемым")
    case _:
        print("Другой код")

While языка python (цикл while в python)

Типичные ошибки:

  • Использование guard для проверки типа, который уже проверен шаблоном, избыточно. Например, case x if isinstance(x, int): - лучше использовать case int(x):.
  • Guard вычисляется только после успешного совпадения шаблона. Если условие ложно, проверка переходит к следующему case.

Как объединить несколько шаблонов (OR-паттерн)?

Символ | (вертикальная черта) позволяет указать несколько альтернативных шаблонов в одном case. Сработает первый подходящий из перечисленных.

day = "сб"
match day:
    case "сб" | "вс":
        print("Выходной")
    case _:
        print("Будний день")

циклы в python конструкции (циклы while в python)

Выходной

Типичные ошибки:

  • Внутри OR-паттерна нельзя использовать захват переменных, потому что неизвестно, какая из альтернатив сработает. Например, case "a" | x: - ошибка. Нужно либо использовать литералы, либо захватывать только за пределами OR.

Как использовать универсальный шаблон и игнорируемые переменные?

Символ _ совпадает с любым значением, но не связывает его с переменной. Также можно использовать *_ для игнорирования элементов последовательности.

numbers = [1, 2, 3, 4, 5]
match numbers:
    case [first, *_]:
        print(f"Первый элемент: {first}")
Первый элемент: 1

Несколько игнорируемых переменных можно обозначить как _, но каждая отдельная _ считается новой переменной. Для множественного игнорирования проще использовать *_ или **_ для словарей.

Типичные ошибки:

  • Забывают, что _ - это обычный идентификатор, и в некоторых контекстах (например, в интерактивной консоли) его можно перезаписать. Лучше использовать *_ или неиспользуемые имена вроде _ignored.

Как сопоставить со словарём?

Шаблон словаря позволяет проверять наличие ключей и извлекать их значения. Синтаксис: {"key1": переменная, "key2": переменная}.

data = {"name": "Анна", "age": 30}
match data:
    case {"name": name, "age": age}:
        print(f"{name}, {age} лет")
    case {"name": name}:
        print(f"Только имя: {name}")
Анна, 30 лет

Для произвольных оставшихся ключей используйте **rest.

match {"a": 1, "b": 2, "c": 3}:
    case {"a": a, **others}:
        print(f"a={a}, остальное: {others}")
a=1, остальное: {'b': 2, 'c': 3}

Типичные ошибки:

  • Шаблон словаря требует, чтобы ключи были литералами (строки, числа, булевы). Динамические ключи не поддерживаются.
  • Шаблон совпадает, если все указанные ключи присутствуют, но не проверяет отсутствие других ключей - если нужно точное соответствие, используйте guard.

Как создавать вложенные шаблоны?

Шаблоны можно комбинировать произвольным образом: последовательности внутри классов, словари внутри кортежей и т.д.

expr = ("add", 5, 3)
match expr:
    case ("add", x, y):
        print(f"Сумма {x + y}")
    case ("sub", x, y):
        print(f"Разность {x - y}")
    case _:
        print("Неизвестная операция")
Сумма 8

Пример с вложенными классами:

class Point:
    __match_args__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Line:
    __match_args__ = ("start", "end")
    def __init__(self, start, end):
        self.start = start
        self.end = end

line = Line(Point(0,0), Point(3,4))
match line:
    case Line(Point(0,0), Point(x,y)):
        print(f"Линия от начала до ({x},{y})")
Линия от начала до (3,4)

Типичные ошибки:

  • Слишком глубокое вложение ухудшает читаемость. В таких случаях лучше разбить проверку на несколько match или использовать guard с дополнительной логикой.
  • Забывают указать __match_args__ для классов, если хотят использовать позиционные аргументы в шаблоне.

Расширенные примеры использования match-case

Разбор AST (абстрактного синтаксического дерева)

match-case идеально подходит для обработки узлов дерева выражений. Рассмотрим простой калькулятор.

Пример
from dataclasses import dataclass

@dataclass
class Num:
    value: int

@dataclass
class Add:
    left: object
    right: object

@dataclass
class Mul:
    left: object
    right: object

def eval_expr(expr):
    match expr:
        case Num(value):
            return value
        case Add(left, right):
            return eval_expr(left) + eval_expr(right)
        case Mul(left, right):
            return eval_expr(left) * eval_expr(right)
        case _:
            raise ValueError("Неизвестный узел")

# Пример
expr = Add(Num(2), Mul(Num(3), Num(4)))
print(eval_expr(expr))  # 2 + (3*4) = 14
14

Здесь match автоматически проверяет тип узла и извлекает атрибуты благодаря __match_args__ (в датаклассах он генерируется автоматически).

Обработка команд с аргументами

Предположим, есть список команд, каждая из которых может быть строкой с параметрами.

Пример
def process_command(cmd):
    match cmd.split():
        case ["quit"]:
            print("Выход из программы")
        case ["move", x, y]:
            print(f"Перемещение в ({x}, {y})")
        case ["add", *items]:
            numbers = list(map(int, items))
            print(f"Сумма = {sum(numbers)}")
        case _:
            print("Неизвестная команда")

process_command("move 10 20")
process_command("add 1 2 3 4")
process_command("quit")
Перемещение в (10, 20)
Сумма = 10
Выход из программы

Сопоставление с вложенными словарями и guards

Пример разбора JSON-ответа API.

Пример
response = {
    "status": "success",
    "data": {"user": {"id": 123, "name": "Иван"}}
}

match response:
    case {"status": "success", "data": {"user": {"id": user_id, "name": name}}}:
        print(f"Пользователь {name} (ID {user_id})")
    case {"status": "error", "message": msg}:
        print(f"Ошибка: {msg}")
    case _:
        print("Неизвестный формат ответа")
Пользователь Иван (ID 123)

Использование match в цикле для фильтрации

Можно обрабатывать элементы списка, каждый раз применяя сопоставление.

Пример
data = [1, "hello", (2, 3), {"key": "value"}, 3.14]
for item in data:
    match item:
        case int(x):
            print(f"Целое: {x}")
        case str(s):
            print(f"Строка: {s}")
        case (a, b):
            print(f"Кортеж из двух элементов: {a}, {b}")
        case dict(d):
            print(f"Словарь с {len(d)} элементами")
        case _:
            print("Другой тип")
Целое: 1
Строка: hello
Кортеж из двух элементов: 2, 3
Словарь с 1 элементами
Целое: 3.14  # float не совпал с int, поэтому перешло на _

Комбинация OR с классами

В одном case можно перечислить несколько классов.

Пример
class Cat:
    pass
class Dog:
    pass

animal = Dog()
match animal:
    case Cat() | Dog():
        print("Домашнее животное")
    case _:
        print("Дикое животное")
Домашнее животное

Обработка ошибок с помощью guard по типу исключения

Хотя match не предназначен для обработки исключений, можно использовать его в блоке except.

Пример
import sys

try:
    1/0
except ZeroDivisionError as e:
    # Использование match для детализации
    match str(e):
        case "division by zero":
            print("Попытка деления на ноль")
        case _:
            print("Другая ошибка с нулём")
Попытка деления на ноль

Однако чаще match применяют к самому объекту исключения.

Пример
try:
    raise ValueError("некорректное значение", 42)
except ValueError as e:
    match e.args:
        case (msg, code):
            print(f"Код ошибки: {code}, сообщение: {msg}")
        case (msg,):
            print(f"Сообщение: {msg}")
Код ошибки: 42, сообщение: некорректное значение

Парсинг простых математических выражений (строковый ввод)

Пример разбора строки вида "2 + 3" с помощью match и split.

Пример
expr = "15 * 4"
match expr.split():
    case [a, "+", b]:
        print(f"{a} + {b} = {int(a) + int(b)}")
    case [a, "-", b]:
        print(f"{a} - {b} = {int(a) - int(b)}")
    case [a, "*", b]:
        print(f"{a} * {b} = {int(a) * int(b)}")
    case [a, "/", b]:
        print(f"{a} / {b} = {int(a) / int(b)}")
    case _:
        print("Неверный формат")
15 * 4 = 60

Обратите внимание: в реальном проекте стоит добавить проверку возможности преобразования в int, использовать try/except.

Сопоставление с константами из перечисления (Enum)

Пример
from enum import Enum

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

c = Color.RED
match c:
    case Color.RED:
        print("Красный")
    case Color.GREEN:
        print("Зелёный")
    case Color.BLUE:
        print("Синий")
Красный

Здесь Color.RED - это литерал-константа, а не захват переменной, так как это атрибут класса.

Конструкция match-case в Python - comments

En
Match case python (python)