Перейти к основному содержимому

Сопоставление с образцом (match / case)

Разработчику

См. также: Управляющие конструкции · Типы данных · ООП в Python


Назначение

Начиная с Python 3.10, конструкция match / case (structural pattern matching) позволяет ветвить выполнение не только по булевому условию, но и по форме значения — константа, тип, распаковка последовательности, поля словаря, атрибуты объекта.

это инструмент для разбора сообщений, статусов API, узлов AST, вариантов ответа дискриминированных типов.


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

def http_status(code: int) -> str:
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500 | 502 | 503:
return "Server error"
case _:
return "Unknown"

Разбор примера:

  • match code берёт одно значение code и последовательно проверяет его по веткам case.

  • case 500 | 502 | 503 использует объединение литералов через |.

  • Выполняется только первый подошедший case, остальные не проверяются.

  • case _ работает как ветка по умолчанию и покрывает все прочие коды.

  • case _ — ветка по умолчанию (как else);

  • case 500 | 502 — несколько литералов через | (логическое "или" внутри образца).

match вычисляет одно выражение слева и сравнивает с образцами сверху вниз до первого совпадения.


Сопоставление с типами

def describe(value):
match value:
case int() if value >= 0:
return f"неотрицательное целое {value}"
case int():
return f"отрицательное целое {value}"
case str() if not value:
return "пустая строка"
case str() as text:
return f"строка длины {len(text)}"
case _:
return "другой тип"

Разбор примера:

  • int() и str() в case означают проверку типа (isinstance).
  • Часть if value >= 0 — это guard, дополнительная фильтрация после совпадения по образцу.
  • case str() as text одновременно проверяет тип и сохраняет значение в text.
  • Такой стиль удобен, когда нужно разбирать разные типы в одной точке входа.

Класс в образце (int(), str()) проверяет тип через isinstance. Условие после if называется guard — образец подходит только если guard истинен.


Последовательности и распаковка

def head_tail(items):
match items:
case []:
return "пусто"
case [x]:
return f"один элемент: {x}"
case [first, *rest]:
return f"голова {first}, хвост из {len(rest)}"

Разбор примера:

  • case [] срабатывает только для пустой последовательности.
  • case [x] срабатывает для последовательности ровно из одного элемента.
  • case [first, *rest] забирает первый элемент в first, остальные в список rest.
  • Механизм распаковки здесь такой же, как в присваивании first, *rest = items.
ОбразецСмысл
[x, y]Ровно два элемента
[first, *rest]Первый + остаток в список rest
(_, _, third)Третий элемент кортежа

Работает с list, tuple и другими последовательностями при сопоставлении по структуре.


Словари и ключевые поля

def parse_event(event: dict):
match event:
case {"type": "click", "x": x, "y": y}:
return f"клик в ({x}, {y})"
case {"type": "key", "code": code}:
return f"клавиша {code}"
case {"type": str(t)}:
return f"неизвестный тип {t}"
case _:
return "не событие"

Разбор примера:

  • case {"type" — "click", "x" — x, "y" — y} требует наличие ключей type, x, y.
  • Значения по ключам x и y связываются с одноимёнными переменными.
  • case {"type" — str(t)} проверяет, что в type лежит строка, и сохраняет её в t.
  • Это удобный способ парсить входные словари без ручных if и get(...).

Имена ключей в образце — обязательные для совпадения; лишние ключи в словаре не мешают (в отличие от некоторых языков с "закрытыми" образцами).


Классы и dataclass

Код ITЗагрузка примера кода…

Разбор примера:

  • @dataclass автоматически создаёт __init__ и служебные методы для Point.
  • В match можно сопоставлять объект по именованным атрибутам: x=..., y=....
  • Guard if x >= 0 and y >= 0 добавляет условие для конкретной четверти.
  • Ветка case Point(x=x, y=y) ниже сработает для остальных точек класса Point.

Сопоставление идёт по атрибутам, не по иерархии наследования (для иерархии классов по-прежнему удобен полиморфизм — см. ООП).


AS — привязка имени

match response:
case {"status": 200, "data": data} as ok:
process(ok["data"])
case {"status": code, "error": msg}:
log_error(code, msg)

Разбор примера:

  • Первая ветка требует статус 200 и ключ data.
  • as ok сохраняет весь совпавший объект response для дальнейшего использования.
  • Во второй ветке код и текст ошибки извлекаются в code и msg.
  • Такой шаблон часто применяют для разбора ответов API.

as ok сохраняет всё совпавшее значение response, а не только извлечённые части.


match и if/elif

Критерийif / elifmatch / case
Простое сравнениеПредпочтительноИзбыточно
Много веток по одной переменнойДлинные цепочкиЧитаемее
Распаковка структурВложенные if и индексыНативно в образце
Произвольные условия между веткамиЛюбые выраженияТолько guards на case
Версия PythonЛюбая3.10+

Для кода, который должен работать на 3.9 и ниже, match недоступен — оставляйте if/elif или словари-диспетчеры.


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

  1. Забыли case _ — при несовпадении возникнет MatchError (аналог необработанного случая).
  2. Изменяемые значения — образец сопоставляет текущее состояние; список может измениться после match, если на него есть другие ссылки.
  3. Порядок веток — более общий образец сверху "перехватит" частные случаи; специфичные case ставят выше общих.

Практический шаблон для API-сообщений

match/case хорошо работает там, где нужно разобрать несколько форматов входящих данных:

def route_event(event: dict) -> str:
match event:
case {"kind": "user.created", "payload": {"id": user_id}}:
return f"user:{user_id}"
case {"kind": "order.paid", "payload": {"order_id": order_id}}:
return f"order:{order_id}"
case {"kind": str(kind)}:
return f"unsupported:{kind}"
case _:
return "invalid"

Разбор примера:

  • Ветки сопоставляют событие по полю-дискриминатору kind.
  • Вложенный словарь payload разбирается прямо в образце без промежуточных проверок.
  • Для неизвестных kind возвращается unsupported:<kind>.
  • Для некорректной структуры (нет ожидаемых полей) срабатывает case _.

Такой код легче сопровождать, чем длинные цепочки вложенных if при росте количества событий.


Где оставлять if, а где match

  • if лучше для 1-2 условий и простых булевых проверок.
  • match лучше для разбора структуры, вариантов и паттернов.
  • В команде полезно договориться о едином стиле для обработчиков событий и команд.

Важно помнить, что match доступен только в Python 3.10+.


Уточнение по несовпадению образцов

Если ни один case не подошёл, Python просто продолжает выполнение после блока match. Исключение автоматически не выбрасывается, поэтому ветка case _ полезна для явной обработки "прочих" сценариев.


Связанные материалы