Match-case: мощная замена if-elif-else
Что такое 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) = 1414
Здесь 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 - это литерал-константа, а не захват переменной, так как это атрибут класса.