Сопоставление с образцом (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 / elif | match / case |
|---|---|---|
| Простое сравнение | Предпочтительно | Избыточно |
| Много веток по одной переменной | Длинные цепочки | Читаемее |
| Распаковка структур | Вложенные if и индексы | Нативно в образце |
| Произвольные условия между ветками | Любые выражения | Только guards на case |
| Версия Python | Любая | 3.10+ |
Для кода, который должен работать на 3.9 и ниже, match недоступен — оставляйте if/elif или словари-диспетчеры.
Типичные ошибки
- Забыли
case _— при несовпадении возникнетMatchError(аналог необработанного случая). - Изменяемые значения — образец сопоставляет текущее состояние; список может измениться после
match, если на него есть другие ссылки. - Порядок веток — более общий образец сверху "перехватит" частные случаи; специфичные
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 _ полезна для явной обработки "прочих" сценариев.