match-case: сопоставление структурных шаблонов Python

match-case: сопоставление структурных шаблонов

Условные конструкции есть практически в каждом языке программирования, и Python - не исключение. Они позволяют выполнять различные действия, в зависимости от того, истинно ли определенное условие. Python долгое время был ограничен стандартными операторами if, elif и else, но среди разработчиков давно возник запрос на операторы сопоставления с шаблонами (англ. pattern matching statement). Обычные условные блоки могут разрастаться до монструозных конструкций, когда нам всего-то надо проверить какую-то переменную на несколько условий. Такой шаблонный код является плохой практикой, и во многих современных языках существуют операторы switch и case, или их аналоги, сильно облегчающие работу программиста. Начиная с версии 3.10 в Python появились свои операторы match и case, вдохновленные аналогами из языков Scala и Erlang.

Синтаксис

Конструкция должна состоять из инструкции match, за которым следует субъект (значение), и нескольких условий case. За каждым case следует паттерн, с которым будет сравниваться субъект. Опционально, за паттерном может следовать "страж" — условие, которое проверяется, если шаблон подошел. Далее следует блок кода, исполняемый при выполнении условий. Паттерном называется новая синтаксическая конструкция. Выглядит она точно так же, как создание объекта, но делает абсолютно противоположное — извлекает компоненты субъекта по определенному шаблону. Использование паттернов позволяет упростить код, особенно если нам нужно проверять типы данных.

Давайте перепишем функцию create_dot, создающую двухмерную точку класса Dot по двум координатам, взятым из кортежа или объекта класса Point.

def create_dot(value):
    if isinstance(value, tuple) and len(value) >= 2:
        return Dot(value[0], value[1]), value[2:]
    elif isinstance(value, Point) and value.z == 0:
        return Dot(value.x, value.y)
    else:
        return TypeError(
                "Невозможно создать точку"
        )

В старой версии мы видим привычную конструкцию if-elif-else. Как видите, нам приходится проверять сразу несколько утверждений и объединять их логическими операторами. Конструкция match-case будет гораздо лаконичнее.

def create_dot(value):
    match value:
        case (x, y, *rest):
            return Dot(x, y), rest
        case Point(x=x, y=y, z=0):
            return Dot(x, y)
        case _:
            return TypeError(
                "Невозможно создать точку"
            )


dot, rest = create_dot((1, 2, 3, 4))
print(dot.x, dot.y, rest)
# 1 2 [3, 4]
dot = create_dot(Point(1, 2, 0))
print(dot.x, dot.y)
# 1 2
print(create_dot('0, 1'))
# Невозможно создать точку

С увеличением количества необходимых проверок, match-case становится все более предпочтительным вариантом.

Паттерны

Для разных целей можно использовать разные шаблоны. Буквальный паттерн (англ. literal) - самый простой из них. Это число, строка, булево значение или None

match value:
    case 0:
        ...
    case "zero":
        ...
    case True:
        ...

Паттерн захвата (англ. capture) позволяет захватить переменную. Захваченную переменную можно использовать внутри соответствующего блока.

match value
    case x:
        print(f'The x is {x}')
    case [a, b]:
        print(a + b)

Подстановочный паттерн (англ. wildcard) обозначается нижним подчеркиванием - _. Он подходит к любому субъекту, но не может захватить какие-то переменные субъекта. Его часто используют как замену else.

Паттерны классов используются для доступа к аттрибутам класса.

match frame
    case LeftFrame(size, color="orange"):
        ...

Паттерны можно комбинировать при помощи вертикальной черты |, которая эквивалентна or.

match n
    case (0 | 1):
        ...

В паттернах может быть удобно использовать моржовый оператор:

match line:
    case Line(start := Point(x, y), end) if start == end:
        print("Точка с координатами {x}, {y}")

Страж

Страж (англ. guard) - условное выражение, позволяющее выполнить блок кода только если оно возвращает True.

match value
    case [x, *rest] if x == 0:
        ...
    case [x, *rest] if x > 0:
        ...
    case _:
        ...

Стражи не допустимы во вложенных паттернах. Выражения типа [x if x == 0] будут вызывать SyntaxError.

Заключение

Сопоставление структурных шаблонов может сильно упростить проверки утверждений. Более лаконичный синтаксис делает чтение блоков match-case более интуитивным, чем if-elif-else. Паттерны не только упрощают код, но и позволяют более гибко использовать субъекты. Эта конструкция особенно полезна, если субъекты могут принадлежать к разным типам.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Функторы в Python Python
Продвинутый
Функторы в Python

Функциональные объекты, или функторы в программировании — объекты, которые можно вызывать, подобно функциям. В этой статье мы разберемся, как создавать функторы и какие возможности они открывают.

2022-10-29
Шпаргалка по модулю itertools Шпаргалки
Продвинутый
Шпаргалка по модулю itertools

Шпаргалка по всем функциям модуля itertools, создающим разнообразные итераторы.

2022-10-28