Сопоставление с образцом: оператор match в Python
Оператор match case (также известный как сопоставление с образцом) появился в Python 3.10. Он позволяет компактно и наглядно описывать ветвления, основанные не только на равенстве значений, но и на структуре данных. Основной синтаксис:
match subject:
case pattern1:
# действия для pattern1
case pattern2:
# действия для pattern2
case _:
# действия по умолчаниюоператор case в python (оператор case (match) в python)
Основной пример: обработка числового значения
Самый частый сценарий – замена цепочки if-elif на match. Рассмотрим функцию, возвращающую название дня недели по номеру:
def day_name(day: int) -> str:
match day:
case 1:
return "Понедельник"
case 2:
return "Вторник"
case 3:
return "Среда"
case 4:
return "Четверг"
case 5:
return "Пятница"
case 6:
return "Суббота"
case 7:
return "Воскресенье"
case _:
return "Неизвестный день"Match case python (конструкция match-case в python)
Каждый case проверяет значение subject на равенство. Ветка _ срабатывает, если ни один из предыдущих паттернов не подошёл. Это эффективно заменяет if-elif-else.
Как реализовать сопоставление значений без использования match?
Классическая конструкция if-elif-else:
def day_name_if(day: int) -> str:
if day == 1:
return "Понедельник"
elif day == 2:
return "Вторник"
elif day == 3:
return "Среда"
elif day == 4:
return "Четверг"
elif day == 5:
return "Пятница"
elif day == 6:
return "Суббота"
elif day == 7:
return "Воскресенье"
else:
return "Неизвестный день"
операторы ветвления в языке python (условные операторы в python)
Разница в читаемости невелика, но match выигрывает, когда нужно одновременно анализировать несколько переменных или структуру.
Как применить словарь для выбора действия по значению?
Словарная диспетчеризация – ещё один способ уйти от длинных if. Ключи – ожидаемые значения, значения – вызываемые объекты или строки:
day_names = {1: "Понедельник", 2: "Вторник", 3: "Среда", 4: "Четверг",
5: "Пятница", 6: "Суббота", 7: "Воскресенье"}
def day_name_dict(day: int) -> str:
return day_names.get(day, "Неизвестный день")Return s s python (оператор return в python)
Такой подход очень быстр, но не позволяет проверять типы или структуры данных.
Как проверить структуру составного объекта без match?
До появления match для анализа кортежей или списков приходилось распаковывать и проверять каждый элемент вручную:
point = (10, 20)
if isinstance(point, tuple) and len(point) == 2 and point[0] == 0:
print("Точка на оси Y")
elif isinstance(point, tuple) and len(point) == 2 and point[1] == 0:
print("Точка на оси X")
else:
print("Обычная точка")
С match та же логика записывается лаконичнее (см. examples_adv).
Типичные ошибки и способы их решения
- Неполное покрытие. Если не указана ветка
_, а ни один паттерн не совпал, Python выбрасываетMatchError. Решение – всегда добавлятьcase _:(или использовать|для полного перекрытия). - Порядок веток. Первый совпавший case выполняется, остальные игнорируются. Специфичные паттерны должны идти до общих. Например,
case _:в начале перекроет все остальные. - Привязка переменной. Внутри case можно присвоить значение части объекта (например,
case x:). Если после этого попытаться использоватьxвне case, будет ошибкаNameError. Решение – объявить переменную заранее или использоватьasс явным захватом. - Guard (if) с побочными эффектами. Условие в guard выполняется каждый раз при проверке ветки. Не стоит помещать туда вызовы, меняющие состояние программы.
- Сравнение с None, True, False. Эти константы являются паттернами, а не литералами. Поэтому
case None:сработает именно на объектNone, а не на строку "None". Это удобно, но может запутать новичков.
Сопоставление с образцом открывает новые возможности для обработки структурных данных и сокращает объём шаблонного кода.
Расширенные примеры использования match
Пример 1: сопоставление с кортежами разного размера
def describe_point(pt):
match pt:
case (0, 0):
return "Начало координат"
case (0, y):
return f"На оси Y, y={y}"
case (x, 0):
return f"На оси X, x={x}"
case (x, y):
return f"Точка ({x}, {y})"
case _:
return "Не точка (не кортеж из двух элементов)"
print(describe_point((0, 5)))
print(describe_point((3, 0)))
print(describe_point((2, 7)))
print(describe_point("строка"))
На оси Y, y=5 На оси X, x=3 Точка (2, 7) Не точка (не кортеж из двух элементов)
Паттерны с привязкой переменных (y, x) позволяют сразу извлечь значения. Ветка _ обрабатывает любой другой тип.
Пример 2: работа со списками и отбрасывание элементов
def analyze_list(lst):
match lst:
case []:
return "Пустой список"
case [first, *rest]:
return f"Первый элемент: {first}, остальные: {rest}"
case _:
return "Не список"
print(analyze_list([1, 2, 3]))
print(analyze_list([]))
Первый элемент: 1, остальные: [2, 3] Пустой список
Звёздочка *rest захватывает оставшуюся часть списка. Если нужен только первый элемент, можно использовать case [first, *_]:.
Пример 3: сопоставление с классами и атрибутами
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
@dataclass
class Circle:
center: Point
radius: float
def what_shape(obj):
match obj:
case Point(x=0, y=0):
return "Точка в начале координат"
case Point():
return f"Обычная точка с x={obj.x}, y={obj.y}"
case Circle(center=Point(x, y), radius=r):
return f"Окружность с центром ({x},{y}) и радиусом {r}"
case _:
return "Неизвестная фигура"
p = Point(0, 0)
c = Circle(Point(3, 4), 5)
print(what_shape(p))
print(what_shape(c))
Точка в начале координат Окружность с центром (3,4) и радиусом 5
Паттерны классов проверяют тип объекта и могут извлекать значения атрибутов по именам. Вложенные паттерны (как Circle(center=Point(x, y), ...)) проверяют сразу структуру.
Пример 4: использование оператора OR (|)
def classify_code(c):
match c:
case 200 | 201 | 204:
return "Успех"
case 400 | 404:
return "Ошибка клиента"
case 500 | 502 | 503:
return "Ошибка сервера"
case _:
return "Другой код"
print(classify_code(200))
print(classify_code(404))
Успех Ошибка клиента
Вертикальная черта объединяет несколько литералов в один паттерн. Все они должны быть константами (или захваченными переменными).
Пример 5: захват всего совпадения через as
def log_value(val):
match val:
case (0 as zero) | (0.0 as zero): # синтаксис: as нельзя в OR напрямую
return f"Обнаружен ноль: {zero}"
# правильный вариант: case (0 | 0.0) as zero:
# но Python 3.10-3.12 допускает только case as name
# Вместо этого используем guard:
# pass
На самом деле использовать as в OR некорректно. Правильная форма – case (0 | 0.0) as zero:. Пример ниже демонстрирует корректный захват:
def analyze_item(item):
match item:
case tuple() as t:
return f"Кортеж длиной {len(t)}"
case list() as lst:
return f"Список длиной {len(lst)}"
case _:
return "Другой тип"
print(analyze_item((1, 2)))
print(analyze_item([5]))
Кортеж длиной 2 Список длиной 1
Пример 6: guard (дополнительное условие)
def parse_number(s):
match s:
case str() if s.isdigit():
return int(s)
case str() if s.replace('.', '', 1).isdigit():
return float(s)
case _:
return None
print(parse_number("123"))
print(parse_number("3.14"))
print(parse_number("abc"))
123 3.14 None
Guard позволяет наложить дополнительное условие после if. Он вычисляется только если паттерн совпал.
Пример 7: вложенный match (сопоставление внутри сопоставления)
def deep_match(data):
match data:
case ('point', (0, 0)):
return "Начало координат"
case ('point', (x, y)) if x == y:
return f"Точка на диагонали ({x},{y})"
case ('circle', {'center': (x, y), 'radius': r}):
return f"Окружность с центром ({x},{y}) и радиусом {r}"
case _:
return "Неизвестная структура"
print(deep_match(('point', (5, 5))))
print(deep_match(('circle', {'center': (1,2), 'radius': 3})))
Точка на диагонали (5,5) Окружность с центром (1,2) и радиусом 3
Паттерны могут включать словари, кортежи, списки и произвольную вложенность. match автоматически проверяет и извлекает нужные части.
Эти примеры показывают гибкость сопоставления с образцом. В реальных проектах match упрощает разбор AST, обработку команд, валидацию данных и многие другие задачи.