Pattern matching в python 3.10+

Vershitel_sudeb

Vershitel sudeb
Команда форума
Модератор
Мар 17, 2021
932
208
43
20
Москва
--- Pattern matching

С релизом python 3.10 появился pattern matching, собственно и являющийся главной особенностью этого релиза. Возможно кто-то уже встречал его упрощенную версию - switch-case - в других языках программирование, но pattern matching это другое, это намного круче.

Так что же это такое? Начну с самого простого примера, раньше для сравнения с разными элементами использовалась конструкция вида if-else:

Python:
a = input('Ввидите имя пользователя: ')

if a == 'danila':
    print('Привет, Админ!')
elif a == 'anna':
    print('Привет, Анна!')
else:
    print('Пользователь не найден')

Теперь мы можем написать это используя pattern matching:

Python:
a = input('Введите имя пользователя: ')

match a:  # Указываем что сравнивать
    case 'danila':  # Если a == 'danila'
        print('Привет, Админ!')
    case 'anna':  # Если a == 'anna'
        print('Привет, Анна!')
    case _:  # Во всех остальных случаях
        print('Пользователь не найден')

Несколько важных моментов:

  1. _ - По умолчанию равен чему угодно, любым данным, по этому case _ это то же самое, что и else
  2. В отличии от некоторых других яп, в python реализации после успешного выполнения case консрукция сразу завершается, не сравнивая входящие данные с шаблонами в других case

Если посмотреть на этот пример, может показаться, что это обычный switch-case, не более, но это просто пример для базового понимания работы конструкции, перейдем к более интересным примерам.



И так, во первых, pattern matching поддерживает распаковку list и tupe (если передать переменные просто через , то это тоже будет считаться как кортеж):

Python:
mas = [1, 2, 3, 4, 5]

match mas:
    # Если имеет РОВНО 4 элемента, то он распакуется в указанные переменные
    # Можно обернуть в [], тут, можно в (), а можно вообще не оборачивать, как дальше
    case [b, c, d, e]:
        print(b)
    # При любом количестве элементов распакует первый элемент в b
    # А остальные в виде списка в c
    case b, *c:
        print(c)  # [2, 3, 4, 5]
    # Немного непривычно, но сборщик элементов может находиться на любой позиции
    case b, *c, d:
        print(*c)  # [2, 3, 4]
    # А можно вообще не писать сборщик
    case b, c, :
        print(b, c)  # 1 2
    # Хотите проверить что массив состоит из 3-х элементов, но 1-й и 3-й вам не нужны?
    case _, b, _:
        print(b)
    # А может вы хотите проверить, что 2-й элемент массива это 2?
    case _, 2, *_:
        print('second elem: 2')

Важный момент - сравнение с шаблоном считается успешным, только если количество элементов у входящего объекта и количество переменных для распаковки одинаковое



Во вторых, можно распаковывать и сравнивать map объекты, например словари:

Python:
men = {
    'name': 'danila',
    'nickname': 'DaSh-More',
    'age': 18,
    'moderator': True,
    'posts_id': [
        11111,
        22222,
        33333
    ]
    }

match men:
    # Если есть ключи name и nickname
    # 'danila' распакуется в переменную name
    # При чем неважно, сколько ключей в словаре, главное что есть указанные
    case {'name': name, 'nickname': _}:
        print(name)
    # Можно сравнивать более глубокие данные
    case {'posts_id': [11111, *n]}:
        print(n)  # [22222, 33333]
    # Можно распаковывать остальные ключи в отдельную переменную
    case {'name':_, **data}:
        print(data)

Важный момент, в отличии от распаковки списков и кортежей, при сравнении словарей проверяется только наличие указанных ключей, если в словаре ключей больше, но при этом есть нужные (указанные в шаблоне), сравнение все равно пройдет успешно



И так, я уже рассказал про сравнение списков и словарей, теперь немного о проверке типа входящих данных

Python:
mas = [1, 2, 3, 4, 5]

match mas:
    # Чтобы проверить тип объекта, надо просто вызвать создание этого объекта
    case list():
        print(mas)
    # Если в скобках указать переменную, объект запишется в нее
    case list(b):
        print(c)
Важный момент, если вы используете не стандартный тип данных, у вас может не получиться его проверить.
Это не значит что это невозможно, просто работает это немного сложней и тут я это разбирать не буду.
Подробнее тут: https://www.python.org/dev/peps/pep-0636/#adding-a-ui-matching-objects

При проверке класса не создается объект класса


--- Дополнительные условия
Чтобы задать дополнительные условия, можно использовать if, который принято называть guard (защита, ограничение)

Python:
mas = [1, 2, 3, 4, 5]

match mas:
 
    # Если передан массив, и при этом его длина > 3
    case list(b) if len(b) > 3:
        print(b)
Указанные в if условия будут проверяться только если совпадает шаблон, по этому можно не опасаться, что передав например в mas число, мы получим ошибку на этапе применения функции len к числу, до этого этапа дело просто не дойдет


--- Несколько лайфхаков
  1. Если вам надо чтобы несколько шаблонов завершались с одним результатом, иногда можно указать этот результат после всего, а в шаблонах прописать pass:

    Python:
    def test(data):
        match data:
            case 200:
                return True
            case 404:
                pass
            case 402:
                pass
            case 414:
                pass
            case _:
                return None
    
            # При указанных кодах 4.. вернет False
            return False
  2. Чтобы сравнить переменную со значением, ее нужно задать явно

    Python:
    bar = 123
    
    test = 77
    
    match bar:
        # Не сравнит bar и test, а распакует bar в test
        case test:
            print(test)  # 123
        # Чтобы этого избежать, надо использовать либо переменные с точкой
        case math.pi:  # Сравнит bar и pi
            print(bar)
        # Либо писать вот так:
        case b if b == test:
            print(b)
  3. Несколько шаблонов можно указать через |:

    Python:
    bar = [1, 2, 3]
    
    match bar:
        case (1, num, 0) | (num, 2, 3):
            print(num)  # 1
    Важный момент, при таком использовании нескольких шаблонов в которых что-то распаковывается (тут num) переменные в которые что-то распаковывается должны иметь одинаковое название в обоих шаблонах и присутствовать так же в обоих (нельзя в одном шаблоне использовать num, а во втором ее не использовать)
  4. Любую часть можно распаковать в переменную

    Python:
    bar = [1, 2, 3]
    
    match bar:
        # Проверяем что 3-й элемент тоже число, и распаковываем его
        case (1, (2 | '2'), int() as k)
            print(k)  # 3


Комбинируйте все выше перечисленное и будет вам счастье!)

Уроки по pattern matching:

Документация:
 
Последнее редактирование:

Форум IT Специалистов