Курс по Python: https://stepik.org/course/100707
На предыдущих занятиях мы увидели, как можно строить шаблоны проверок для отдельных
переменных и упорядоченных коллекций типа list и tuple. Теперь пришло
время обработки других коллекций типа dict (словарь) и set (множество). Начнем
со словарей.
Давайте
предположим, что на вход блока обработки поступает запрос в виде следующего
словаря:
request = {'url': 'https://proproprogs.ru/', 'method': 'GET', 'timeout': 1000}
Мы хотим выбрать
из него данные по ключам «url» и «method». Шаблон
проверки в этом случае будет выглядеть так:
match request:
case {'url': url, 'method': method}:
print(f"Запрос: url: {url}, method: {method}")
case _: # wildcard
print("неверный запрос")
Обратите
внимание, мы здесь использовали фигурные скобки. Как только мы их прописываем,
шаблон сразу ожидает данные в формате «ключ-значение». Далее, мы говорим, что в
словаре request должны быть два
ключа «url» и «method» с какими то
произвольными значениями. Если эти ключи действительно присутствуют, то
создаются переменные url и method, которые
ссылаются на соответствующие значения ключей. Затем, с помощью print(), мы выводим в
консоль строку с этими значениями переменных.
То есть, данный
шаблон будет срабатывать всякий раз, как только на вход поступает словарь с
ключами «url» и «method». При этом
наличие или отсутствие других ключей не играет никакой роли. Например, в
словаре request есть еще один
ключ «timeout», но, тем не
менее, шаблон срабатывает. Это отличает проверку словарей от проверки кортежей
и списков, когда мы должны были указывать ровно то число элементов, которые в
них ожидаются. Для словарей же достаточно лишь наличие указанных ключей и не
более того.
Далее, если мы
дополнительно хотим сделать проверку на тип данных в значениях словаря, то это
можно сделать следующим образом:
case {'url': str(url), 'method': int() as method}:
Я здесь
специально проверку типа записал двумя разными способами, чтобы напомнить о них.
В первом ключе мы требуем, чтобы была строка, а во втором – чтобы было целое
число. Конечно, сейчас шаблон не сработает, т.к. method является
строкой, но если мы его изменим на число:
request = {'url': 'https://proproprogs.ru/', 'method': 1, 'timeout': 1000}
то он среагирует
на этот словарь.
Записывая после
ключей имена переменных, мы, тем самым, определяем шаблон для произвольных
значений заданного типа (или любого типа, если этих проверок нет). Но можно
записать ключ с конкретным значением, например, так:
case {'url': url, 'method': method, 'timeout': 1000}:
Тогда оператор case отработает
только если ключ 'timeout' принимает значение 1000 и будет
игнорировать все другие значения. Например, если:
request = {'url': 'https://proproprogs.ru/', 'method': 'GET', 'timeout': 2000}
то перейдем в
последний блок case (отбойник). Но, если мы скомбинируем эти два
варианта в одном шаблоне:
case {'url': url, 'method': method, 'timeout': 1000} | {'url': url, 'method': method, 'timeout': 2000}:
то сможем
отлавливать и значение 1000 и значение 2000 для ключа 'timeout'. То есть,
здесь оператор ‘|’ работает так же, как и с упорядоченными коллекциями.
Также мы, при
необходимости, можем прописывать guard (защитника) при обработке словарей.
Например, если в запросе request ожидается не более трех ключей,
то такой шаблон можно записать в виде:
case {'url': url, 'method': method} if len(request) <= 3:
И так далее,
здесь допустимо использовать все те же самые конструкции, которые мы
рассматривали на предыдущих занятиях.
Давайте теперь
предположим, что нам нужно построить шаблон проверки словаря, в котором есть
ключи «url» и «method» и
дополнительно может быть еще не более двух ключей. Как это сделать? Для этого
следует воспользоваться оператором упаковки ** всех остальных данных словаря,
кроме указанных ключей, например, так:
match request:
case {'url': url, 'method': method, **kwargs} if len(kwargs) <= 2:
print(f"Запрос: url: {url}, method: {method}")
case _: # wildcard
print("неверный запрос")
В результате в
переменной kwargs будут
находиться все остальные ключи с их значениями, кроме ключей «url» и «method». А далее, мы просто
проверяем, чтобы словарь kwargs содержал не более двух дополнительных
ключей.
Теперь,
например, для запроса (словаря):
request = {'id': 2, 'url': 'https://proproprogs.ru/', 'method': 'GET', 'timeout': 2000}
шаблон
сработает, а для словаря:
request = {'id': 2, 'url': 'https://proproprogs.ru/', 'method': 'GET', 'timeout': 2000, 'date': 100}
не сработает,
т.к. дополнительно имеем уже три ключа. Причем расположение ключей в словаре не
имеет никакого значения при работе шаблона. Главное, чтобы существовали ключи «url» и «method» и кроме них
было не более двух дополнительных ключей.
По аналогии
можно организовать проверку на наличие строго двух ключей в словаре:
case {'url': url, 'method': method, **kwargs} if not kwargs:
Мы здесь в
защитнике проверяем, чтобы словарь kwargs был пустым.
Давайте теперь
несколько усложним структуру словаря и предположим, что на вход поступают
следующие данные:
json_data = {'id': 2, 'type': 'list', 'data': [1, 2, 3], 'access': True, 'date': '01.01.2023'}
Здесь множество
ключей, причем, один из них (data) может содержать список, если ключ «type» принимает
значение «list». Шаблон
обработки можно записать в виде:
match json_data:
case {'type': 'list', 'data': lst}:
print(f"JSON-данные: type-list: {lst}")
case _: # wildcard
print("неверный запрос")
Первый блок case сработает только
в том случае, если ключ «type» имеет значение «list» и имеется еще
один ключ «data». Ожидается,
что переменная lst в этом случае будет ссылаться на список.
Конечно, для надежности, мы можем сделать дополнительную проверку типа данных:
case {'type': 'list', 'data': list() as lst}:
Тогда
гарантированно, при срабатывании блока case, переменная lst будет принимать
тип list. Видите, как
легко, просто и элегантно мы сделали обработку нужного нам формата данных с
помощью операторов match/case. Гораздо проще,
чем если бы использовали условные операторы if/elif/else. Именно в этом
преимущество конструкции match/case перед условными
операторами.
Или вот, еще
несколько более сложный пример шаблона обработки проверки и обработки входных
данных. Пусть имеется словарь вида:
json_data = {'id': 2, 'access': True, 'info': ['01.01.2023', {'login': '123', 'email': 'email@m.ru'}, True, 1000]}
Нам нужно
проверить наличие ключей 'access' и 'info' и выделить
значение ключа 'access' и email из ключа 'info'. Сделать это
можно следующим образом:
match json_data:
case {'access': access, 'info': [_, {'email': email}, _, _]}:
print(f"JSON: access: {access}, email: {email}")
case _: # wildcard
print("неверный запрос")
Смотрите, как
красиво все выглядит! Мы буквально берем значение ключа 'access' и нужное
значение для email из списка ключа
'info', попутно
проверяя наличие этих ключей и списка в словаре json_data. Причем список
обязательно должен содержать четыре элемента и во втором храниться словарь с
ключом email. Попробуйте
сделать то же самое через операторы if/elif/else и вы увидите,
насколько сложнее будет выглядеть эта же программа! Тогда как конструкция match/case с легкостью
решает поставленную задачу.
Обработка множеств
Вот в целом так
строятся проверки для словарей. Помимо них есть еще одна похожая коллекция –
множество (set). Предположим,
мы бы хотели выбирать все множества, состоящие из трех элементов, например:
Как это можно
сделать? Если прописать по аналогии со словарем:
match primary_keys:
case {a, b, c}:
print(f"Primary Keys: {a}, {b}, {c}")
case _: # wildcard
print("неверный запрос")
то получим
синтаксическую ошибку. Для множеств так записывать шаблоны нельзя. Если
попробовать убрать фигурные скобки, то будут ожидаться на входе кортежи или
списки. Как же быть с множествами? Вариант, фактически, один. Прописать явно
тип set и присвоить
некоторой переменной ссылку на множество:
match primary_keys:
case set() as keys:
print(f"Primary Keys: {keys}")
case _: # wildcard
print("неверный запрос")
А проверку,
чтобы множество содержало ровно три элемента, можно сделать с помощью guard:
case set() as keys if len(keys) == 3:
По сути, только
так мы можем отбирать множества и через guard записывать
некоторые проверки.
На этом мы
завершим наше очередное занятие по конструкции match/case. На следующем
заключительном занятии по этой теме рассмотрим некоторые ограничения и
особенности работы этих операторов, а также посмотрим на некоторые типовые
примеры их использования.
Курс по Python: https://stepik.org/course/100707