W3docs

Оператор match в Python

Структурное сопоставление шаблонов в Python: match/case, литералы, последовательности, словари, классы, охранники и wildcard — с примерами.

В Python 3.10 появилось структурное сопоставление шаблонов через оператор match — мощный способ ветвления по форме и содержимому данных, а не только по равенству. В этой главе рассматривается всё: от базового синтаксиса match/case до сложных шаблонов — разбиения последовательностей, шаблонов словарей, шаблонов классов, охранников и реальных примеров использования.

Перед чтением этой главы рекомендуется быть знакомым с конструкцией if/else в Python, функциями Python и базовыми структурами данных (списками, кортежами, словарями).

Что такое структурное сопоставление шаблонов?

Структурное сопоставление шаблонов позволяет анализировать структуру объекта — его тип, значения полей, форму последовательности — и выполнять разный код в зависимости от того, какой шаблон подходит. Это выходит далеко за рамки простой проверки if x == y.

Рассмотрим маршрутизацию по HTTP-коду статуса. С цепочками if/elif код выглядит так:

if status == 200:
    print("OK")
elif status == 404:
    print("Not Found")
elif status == 500:
    print("Internal Server Error")
else:
    print("Unknown status")

С match намерение выражается чище:

match status:
    case 200:
        print("OK")
    case 404:
        print("Not Found")
    case 500:
        print("Internal Server Error")
    case _:
        print("Unknown status")

Настоящее преимущество проявляется, когда объект сложный — кортеж, словарь или датакласс — и его нужно деструктурировать в процессе сопоставления.

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

match subject:
    case pattern1:
        # runs if subject matches pattern1
    case pattern2:
        # runs if subject matches pattern2
    case _:
        # wildcard — runs if nothing else matched

Правила, которые нужно помнить:

  • match и caseмягкие ключевые слова: они являются ключевыми только в данном контексте и в остальных частях кода могут использоваться как имена переменных.
  • Каждый блок case проверяется по порядку; первое совпадение выигрывает, остальные пропускаются.
  • Блок case _: — это wildcard: он всегда совпадает и служит вариантом по умолчанию.
  • Требуется Python 3.10+. Запуск на Python 3.9 или более ранних версиях вызывает SyntaxError.

Шаблоны литералов

Простейший шаблон сопоставляет конкретное значение: число, строку, True, False или None.

def http_status(status):
    match status:
        case 200:
            return "OK"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return "Unknown status"

print(http_status(200))   # OK
print(http_status(404))   # Not Found
print(http_status(999))   # Unknown status

Шаблоны ИЛИ (|)

Используйте | внутри case, чтобы сопоставить любой из нескольких литералов:

def is_vowel(letter):
    match letter.lower():
        case "a" | "e" | "i" | "o" | "u":
            return True
        case _:
            return False

print(is_vowel("a"))   # True
print(is_vowel("b"))   # False
print(is_vowel("E"))   # True

Шаблоны ИЛИ также работают с числами, None и другими типами литералов.

Шаблоны захвата

Шаблон захвата — это простое имя (не строковый литерал, не составное имя), которое совпадает с чем угодно и привязывает сопоставленное значение к этому имени для использования в теле блока:

def greet(name):
    match name:
        case "Alice":
            return "Hello, Alice!"
        case other:          # captures whatever was passed
            return f"Hello, {other}!"

print(greet("Alice"))    # Hello, Alice!
print(greet("Bob"))      # Hello, Bob!

other здесь — шаблон захвата: он привязывает сопоставленное значение к локальной переменной other. Это похоже на wildcard _, но _ отбрасывает значение, тогда как именованный захват его сохраняет.

Внимание

Простое имя в case — это всегда захват, но не сравнение. Чтобы сравнить со значением константы, определённой в другом месте, используйте составное имя — например, Status.OK — или охранник (case x if x == my_constant:).

Шаблоны последовательностей

Шаблон последовательности сопоставляется со списками, кортежами или любыми последовательностями и одновременно может деструктурировать элементы в переменные.

def process_point(point):
    match point:
        case (0, 0):
            return "Origin"
        case (x, 0):
            return f"On x-axis at {x}"
        case (0, y):
            return f"On y-axis at {y}"
        case (x, y):
            return f"Point at ({x}, {y})"

print(process_point((0, 0)))   # Origin
print(process_point((5, 0)))   # On x-axis at 5
print(process_point((0, 3)))   # On y-axis at 3
print(process_point((2, 4)))   # Point at (2, 4)

Использование * для захвата остатка

*name внутри шаблона последовательности собирает оставшиеся элементы — так же, как при распаковке итерируемых объектов:

def describe_list(items):
    match items:
        case []:
            return "empty list"
        case [single]:
            return f"one item: {single}"
        case [first, *rest]:
            return f"starts with {first!r}, then {len(rest)} more item(s)"

print(describe_list([]))              # empty list
print(describe_list([42]))            # one item: 42
print(describe_list([1, 2, 3, 4]))   # starts with 1, then 3 more item(s)

Используйте [first, *_], если хотите захватить только первый элемент и отбросить остальные.

Шаблоны словарей

Шаблон словаря сопоставляется со словарями (или любым Mapping). Вы указываете только интересующие вас ключи — лишние ключи в объекте игнорируются.

def process_event(event):
    match event:
        case {"type": "click", "button": button}:
            return f"Mouse click: button {button}"
        case {"type": "keypress", "key": key}:
            return f"Key pressed: {key!r}"
        case {"type": action}:
            return f"Other event: {action}"
        case _:
            return "Unknown event"

print(process_event({"type": "click", "button": 1}))
# Mouse click: button 1
print(process_event({"type": "keypress", "key": "Enter"}))
# Key pressed: 'Enter'
print(process_event({"type": "resize", "width": 800}))
# Other event: resize
print(process_event({}))
# Unknown event

Ключевой момент: шаблон словаря не вызывает ошибку из-за лишних ключей в объекте. {"type": "click", "button": button} совпадёт, даже если событие также содержит координаты "x" и "y".

Чтобы захватить оставшиеся пары ключ/значение, используйте **rest:

match event:
    case {"type": "click", **rest}:
        print(f"Click event with extra data: {rest}")

Шаблоны классов

Шаблон класса сопоставляется с экземпляром определённого класса и извлекает его атрибуты. Это особенно удобно с датаклассами, поскольку их атрибуты автоматически доступны по имени.

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

@dataclass
class Circle:
    center: Point
    radius: float

def describe_shape(shape):
    match shape:
        case Point(x=0, y=0):
            return "Point at origin"
        case Point(x=x, y=y):
            return f"Point at ({x}, {y})"
        case Circle(center=Point(x=cx, y=cy), radius=r):
            return f"Circle centered at ({cx}, {cy}) with radius {r}"
        case _:
            return "Unknown shape"

print(describe_shape(Point(0, 0)))           # Point at origin
print(describe_shape(Point(3, 4)))           # Point at (3, 4)
print(describe_shape(Circle(Point(1, 2), 5)))# Circle centered at (1, 2) with radius 5

Обратите внимание на вложенный шаблон класса в ветке Circle: Point(x=cx, y=cy) сопоставляется внутри шаблона Circle. Шаблоны можно комбинировать на произвольную глубину.

Для встроенных типов int, str, float и bool можно использовать позиционные шаблоны с одним аргументом:

def handle_input(value):
    match value:
        case (int() | float()) as number:
            return f"Got a number: {number}"
        case str() as text:
            return f"Got text: {text!r}"
        case _:
            return "Unknown type"

print(handle_input(3.14))    # Got a number: 3.14
print(handle_input("hello")) # Got text: 'hello'
print(handle_input([1, 2]))  # Unknown type

Ключевое слово as (шаблон AS) привязывает всё сопоставленное значение к имени даже после проверки типа.

Охранники

Охранник — это условие if, добавляемое после шаблона. Ветка case совпадает только тогда, когда шаблон подходит и охранник возвращает True.

def classify_number(n):
    match n:
        case 0:
            return "zero"
        case x if x < 0:
            return f"{x} is negative"
        case x if x % 2 == 0:
            return f"{x} is positive and even"
        case x:
            return f"{x} is positive and odd"

print(classify_number(0))    # zero
print(classify_number(-5))   # -5 is negative
print(classify_number(4))    # 4 is positive and even
print(classify_number(7))    # 7 is positive and odd

Охранники вычисляются после того, как структурный шаблон совпал, поэтому захваченные переменные доступны внутри них. Если охранник не срабатывает, последующие ветки case продолжают проверяться.

Wildcard-шаблон _

_ — универсальный перехватчик. Он совпадает с любым значением и ничего не привязывает (значение отбрасывается). Он должен быть последним case в блоке match. Без него match, не нашедший ни одного совпадения, просто ничего не делает — ошибка не возникает.

def describe(value):
    match value:
        case 0:
            return "zero"
        case _:
            return f"something else: {value!r}"

print(describe(0))     # zero
print(describe(99))    # something else: 99
print(describe("hi"))  # something else: 'hi'

_ может встречаться и внутри шаблона, чтобы игнорировать отдельные части:

match point:
    case (_, 0):
        print("On the x-axis (x value doesn't matter)")
    case (0, _):
        print("On the y-axis (y value doesn't matter)")

Комбинирование шаблонов: реальный пример

Шаблоны, описанные выше, можно комбинировать. Вот парсер команд текстового квеста, в котором сочетаются шаблоны последовательностей, охранники и wildcard:

def run_command(command):
    match command.split():
        case ["quit"]:
            return "Quitting"
        case ["go", direction] if direction in ("north", "south", "east", "west"):
            return f"Going {direction}"
        case ["go", direction]:
            return f"Cannot go {direction!r} — try north, south, east, or west"
        case ["get", item]:
            return f"Picking up {item}"
        case ["drop", item]:
            return f"Dropping {item}"
        case ["inventory"]:
            return "Checking inventory"
        case [verb, *args]:
            return f"Unknown command {verb!r} with args {args}"
        case []:
            return "No command entered"

print(run_command("go north"))    # Going north
print(run_command("go up"))       # Cannot go 'up' — try north, south, east, or west
print(run_command("get sword"))   # Picking up sword
print(run_command("drop torch"))  # Dropping torch
print(run_command("quit"))        # Quitting
print(run_command(""))            # No command entered

Читая код сверху вниз, можно сразу понять каждую поддерживаемую команду — для достижения той же ясности с if/elif потребовалось бы значительно больше строк.

match vs. if/elif — когда что выбирать

СценарийЛучший выбор
Простое равенство с несколькими константамиЛюбой; match немного чище
Сопоставление по структуре данных / формеmatch — значительно чище
Деструктуризация значений при сопоставленииmatch — с if это невозможно
Логика только с вычисляемыми условиямиif/elif
Python 3.9 или более ранние версииif/elif (нет match)
Выражение таблицы решений наглядноmatch

match — не замена каждой цепочки if. Когда все ветки проверяют вычисляемые boolean-условия (например, if x > 10 and y < 5), цепочка if/elif более естественна. match показывает себя лучше, когда условие касается формы данных.

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

Имена констант не сопоставляются по значению

Простое имя в case — это всегда захват, но не поиск значения:

STATUS_OK = 200

match response_code:
    case STATUS_OK:          # WRONG — this captures into STATUS_OK, not compares!
        print("Success")

Чтобы сравнить с именованной константой, используйте составное имя (http.HTTPStatus.OK) или охранник:

match response_code:
    case x if x == STATUS_OK:
        print("Success")

match не является исчерпывающим по умолчанию

В отличие от switch в некоторых других языках, match без совпадающей ветки просто ничего не делает. Добавьте case _:, если нужен гарантированный обработчик.

match требует Python 3.10+

Запуск блока match на Python 3.9 или более ранних версиях вызывает SyntaxError: invalid syntax. Проверьте версию с помощью python3 --version. Если нужно настроить современное окружение Python, см. руководство по началу работы с Python.

Шаблоны — не boolean-выражения

Нельзя писать case x > 5: — это охранник, а не шаблон. Структурная часть (case x) должна идти первой, за ней следует необязательный if guard_expression.

Итоги

Тип шаблонаПример синтаксисаЧто сопоставляет
Литералcase 42:Точное значение
ИЛИcase "yes" | "y":Любой из вариантов
Wildcardcase _:Что угодно (значение отбрасывается)
Захватcase x:Что угодно, привязывает к x
Последовательностьcase [a, b, *rest]:Последовательность не менее чем из 2 элементов
Словарьcase {"key": val}:Словарь, содержащий указанные ключи
Классcase Point(x=0, y=y):Экземпляр с совпадающими атрибутами
AScase int() as n:Совпадает и привязывает всё значение
Охранникcase x if x > 0:Шаблон + дополнительное boolean-условие

Практика

Практика
Which Python version first introduced the match statement?
Which Python version first introduced the match statement?
Практика
In a match block, what does a bare variable name in a case clause do?
In a match block, what does a bare variable name in a case clause do?
Практика
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?
Which pattern type would you use to match a dict that contains at least a 'type' key and extract its value?

Теперь, когда вы умеете ветвиться по структуре данных, изучите циклы for в Python для итерации по последовательностям или перечисления enum в Python, чтобы определять типизированные константы, которые хорошо работают с шаблонами классов.

Was this page helpful?