Примеры match/case в Python 3.10 с объяснением
Релиз Python 3.10, вышедший в октябре 2021 года, предложил разработчикам несколько интересных изменений, включая pattern matching statement (оператор сопоставления с шаблонами). Как уверяли авторы PEP 622, на создание этого предложения их вдохновил схожий синтаксис в языках программирования Scala и Erlang.
Если вы еще не знакомы с pattern matching, то у вас сейчас есть хорошая возможность узнать в данной статье, что выполняет эта функция, как происходит совпадение, о предназначении и вариантах использования этого оператора в Python.
Pattern matching: что это такое и зачем он нужен
Согласно пояснениям, изложенным в PEP622, в коде Python иногда требуется сопоставлять данные с типами, обращаться к ним по индексу и применять проверку на тип. При этом нужно проверять не один лишь тип данных, но и их число. А это приводит к тому, что возникает большое количество ответвлений if/else, которые вызывают функции isinstance, len и обращаются к элементам по индексу, атрибуту или ключу. Чтобы избежать подобных усложнений и сократить if/else, появился оператор Pattern matching.
До выпуска Python 3.10 в его синтаксисе отсутствовал простой и надежный способ сравнения значений с с одним из множества вероятных условий. Хотя в других языках такие фичи давно существовали. К примеру, в C и C++ есть конструкция switch/case. Нечто подобное есть и в Rust.
Pattern matching в коде Python реализуется при помощи конструкції match/case. Она позволяет сопоставлять выражение, структуру данных или тип с шаблоном. Проще говоря, Python перебирает все варианты шаблонов, пока не найдет тот шаблон, который соответствует выражению.
Важно не путать match/case и switch/case. Различия между ними в том, что pattern matching и его конструкция match/case — это не обычный оператор для сравнения какой-то переменной со значениями, а готовый механизм для сопоставления данных, их распаковки и управления потоком выполнения.
Использовать match/case можно в следующих ситуациях:
- для упрощения сложных ветвлений (теперь не нужны многочисленные проверки if/elif);
- для сопоставления сложных структур данных с шаблоном;
- для проверки типа переменной и обработки разных типов данных в нескольких вариантах.
Теперь время пришло для изучения нескольких примеров, как этот оператор упрощает написание кода, делая его более читаемым.
Пример 1: Простое сопоставление значений
Для начала посмотрим, как это работает с if/else:
day = "Monday" if day == "Monday" or day == "Tuesday" or day == "Wednesday" or day == "Thursday" or day == "Friday": print("It's a workday.") elif day == "Saturday" or day == "Sunday": print("It's a weekend.") else: print("Unknown day.")
А теперь то же самое, но уже с match/case:
day = "Monday" match day: case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday": print("It's a workday.") case "Saturday" | "Sunday": print("It's a weekend.") case _: print("Unknown day.")
В обоих примерах код проверяет значение переменной day и выводит, является ли этот день рабочим или выходным.
Пример 2: Сопоставление с типами данных
Опять для начала используем if/else. Предположим, у нас есть переменная value, и мы хотим определить ее тип данных, чтобы выполнить соответствующее действие.
value = 42 if isinstance(value, int): print("The value is an integer.") elif isinstance(value, float): print("The value is a float.") elif isinstance(value, str): print("The value is a string.") elif isinstance(value, list): print("The value is a list.") else: print("Unknown type.")
То же самое, но уже с match/case:
value = 42 match value: case int(): print("The value is an integer.") case float(): print("The value is a float.") case str(): print("The value is a string.") case list(): print("The value is a list.") case _: print("Unknown type.")
В этих примерах код проверяет тип данных переменной value и выполняет соответствующее действие. В первой версии используется if/else с функцией isinstance, во второй — конструкция match/case, которая позволяет более лаконично сопоставлять значения с их типами.
Пример 3: Сопоставление с шаблоном в списке
Сначала применяем if/else. Тут у нас есть список из двух элементов, и мы хотим выполнить определенное действие в зависимости от значений этих элементов.
values = [1, 2] if len(values) == 2 and values[0] == 1 and values[1] == 2: print("The list is [1, 2].") elif len(values) == 2 and values[0] == 3 and values[1] == 4: print("The list is [3, 4].") elif len(values) == 2 and values[0] == 5 and values[1] == 6: print("The list is [5, 6].") else: print("Unknown list pattern.")
Тот же самый код, но с match/case:
values = [1, 2] match values: case [1, 2]: print("The list is [1, 2].") case [3, 4]: print("The list is [3, 4].") case [5, 6]: print("The list is [5, 6].") case _: print("Unknown list pattern.")
Здесь код проверяет, соответствует ли список values определенным шаблонам. Сначала используется if/else с условиями для длины и элементов списка, а во второй — конструкция match/case, которая позволяет легко сопоставлять списки с заданными шаблонами.
Пример 4: Сопоставление с шаблоном в словаре
Предположим, у нас есть словарь, и мы хотим выполнить определенное действие в зависимости от значений его ключей.
data = {"name": "Alice", "age": 30} if "name" in data and "age" in data and data["name"] == "Alice" and data["age"] == 30: print("This is Alice, aged 30.") elif "name" in data and "age" in data and data["name"] == "Bob" and data["age"] == 25: print("This is Bob, aged 25.") elif "name" in data and "age" in data and data["name"] == "Charlie" and data["age"] == 40: print("This is Charlie, aged 40.") else: print("Unknown person.")
Тот же пример с использованием match/case:
data = {"name": "Alice", "age": 30} match data: case {"name": "Alice", "age": 30}: print("This is Alice, aged 30.") case {"name": "Bob", "age": 25}: print("This is Bob, aged 25.") case {"name": "Charlie", "age": 40}: print("This is Charlie, aged 40.") case _: print("Unknown person.")
Сначала код проверяет, соответствует ли словарь data определенным шаблонам. В первой версии используется if/else с проверкой наличия ключей и значений, а во второй — конструкция match/case, которая позволяет легко сопоставлять словари с заданными шаблонами.
Пример 5: Сопоставление вложенных структур данных
Предположим, у нас есть вложенная структура данных — словарь, содержащий список, и мы хотим выполнить определенное действие в зависимости от значений этой структуры.
data = { "user": { "name": "Alice", "details": { "age": 30, "hobbies": ["reading", "hiking"] } } } if ( "user" in data and "name" in data["user"] and data["user"]["name"] == "Alice" and "details" in data["user"] and "age" in data["user"]["details"] and data["user"]["details"]["age"] == 30 and "hobbies" in data["user"]["details"] and data["user"]["details"]["hobbies"] == ["reading", "hiking"] ): print("This is Alice, aged 30, who likes reading and hiking.") else: print("Unknown user or different structure.")
А теперь пример с использованием match/case:
data = { "user": { "name": "Alice", "details": { "age": 30, "hobbies": ["reading", "hiking"] } } } match data: case { "user": { "name": "Alice", "details": { "age": 30, "hobbies": ["reading", "hiking"] } } }: print("This is Alice, aged 30, who likes reading and hiking.") case _: print("Unknown user or different structure.")
В этих примерах идет проверка, соответствует ли вложенная структура данных data определенному шаблону. В первом случае используется конструкция if/else с проверками на наличие ключей и значений на каждом уровне вложенности. Во втором случае используется конструкция match/case, которая позволяет намного проще сопоставлять сложные вложенные структуры данных с заданными шаблонами.
Сложный пример кода с использованием match/case в Python
А теперь давайте перейдем от простых примеров кода к более сложным вариантам применения match/case в Python. Фрагмент, который вы сейчас увидите, демонстрирует анализ сложной структуры данных и включает сравнение вложенных структур данных, использование условных проверок (guards) и извлечение значений.
Предположим, у нас есть система, которая предназначена для обработки разных типов сообщений в виде списков и вложенных словарей. Все сообщения имеют тип и способны содержать как данные, так и метаданные. Нам нужно обработать разные типы сообщений различными способами, в том числе с выполнением условных проверок.
def process_message(message): match message: case {"type": "user_action", "action": action, "details": {"user_id": user_id, "timestamp": timestamp}}: return f"User {user_id} performed {action} at {timestamp}" case {"type": "system_event", "event": event, "metadata": {"severity": severity}} if severity > 3: return f"Critical system event: {event} with severity {severity}" case {"type": "notification", "content": {"title": title, "message": msg}, "recipients": recipients}: if "admin" in recipients: return f"Admin notification: {title} - {msg}" else: return f"User notification: {title} - {msg}" case {"type": "batch", "messages": messages}: results = [process_message(msg) for msg in messages] return f"Batch processed with results: {results}" case _: return "Unknown message format" # Пример сообщений для обработки message1 = { "type": "user_action", "action": "login", "details": {"user_id": 42, "timestamp": "2024-08-22T14:00:00Z"} } message2 = { "type": "system_event", "event": "disk_failure", "metadata": {"severity": 5} } message3 = { "type": "notification", "content": {"title": "Maintenance", "message": "Scheduled maintenance at midnight"}, "recipients": ["admin", "user_123"] } message4 = { "type": "batch", "messages": [message1, message2, message3] } # Обработка сообщений print(process_message(message1)) # Output: User 42 performed login at 2024-08-22T14:00:00Z print(process_message(message2)) # Output: Critical system event: disk_failure with severity 5 print(process_message(message3)) # Output: Admin notification: Maintenance - Scheduled maintenance at midnight print(process_message(message4)) # Output: Batch processed with results: [...]
Давайте разберем, что мы видим в этом коде.
- case 1: Выполняет сопоставление сообщения типа “user_action” с вложенной структурой данных. Извлекаются значения action, user_id и timestamp. Затем они используются в строке результата.
- case 2: Выполняет сопоставление сообщения типа “system_event”, но только если значение severity превышает 3 (условие, обозначенное с помощью if severity > 3).
- case 3: Выполняет сопоставление сообщения типа “notification”. В зависимости от наличия “admin” в списке получателей (recipients), формируется разное сообщение.
- case 4: Обрабатывает сообщения типа “batch”, содержащие список сообщений. Для каждого сообщения внутри списка рекурсивно вызывается функция process_message, и результаты собираются в список.
- case _: Собирает все сообщения, не подходящие ни под один из предыдущих шаблонов, и возвращает сообщение об ошибке.
Обработка данных в системе умного дома с помощью match/case в Python
Предположим, нам поручили разработать систему управления умным домом. Система получает данные от датчиков, установленных в разных помещениях. Они передают информацию о температуре, влажности, уровне освещения и другие данные. Затем все эти сообщения поступают в устройства, которые, скорее всего, имеют разную структуру. Наша задача состоит в том, чтобы эффективно обработать сообщения от датчиков, используя конструкцию match/case в Python.
Согласно условиям задачи для каждого типа датчика нужно:
- Извлечь из устройства данные.
- Изменить работу устройства при необходимости (повысить или понизить температуру, включить освещение, отправить сообщение).
- Реализация должна быть выполнена с помощью match/case в Python 3.10.
def process_sensor_data(sensor_data): match sensor_data: # Сообщение от датчика температуры case {"type": "temperature", "value": temp, "unit": "C"} if temp > 30: return f"Warning: High temperature detected: {temp}°C. Turning on the AC." case {"type": "temperature", "value": temp, "unit": "C"}: return f"Temperature is normal: {temp}°C." # Сообщение от датчика влажности case {"type": "humidity", "value": humidity, "unit": "%"} if humidity > 70: return f"Warning: High humidity detected: {humidity}%. Activating dehumidifier." case {"type": "humidity", "value": humidity, "unit": "%"}: return f"Humidity is normal: {humidity}%." # Сообщение от датчика движения case {"type": "motion", "detected": True, "location": location}: return f"Motion detected in {location}. Turning on the lights." case {"type": "motion", "detected": False}: return "No motion detected." # Сообщение от датчика освещения case {"type": "light", "value": level, "unit": "lux"} if level < 50: return f"Low light level detected: {level} lux. Turning on the lights." case {"type": "light", "value": level, "unit": "lux"}: return f"Light level is sufficient: {level} lux." # Обработка неизвестных типов сообщений case _: return "Unknown sensor data received." # Примеры входных данных sensor_data1 = {"type": "temperature", "value": 32, "unit": "C"} sensor_data2 = {"type": "humidity", "value": 75, "unit": "%"} sensor_data3 = {"type": "motion", "detected": True, "location": "living room"} sensor_data4 = {"type": "light", "value": 30, "unit": "lux"} # Обработка данных print(process_sensor_data(sensor_data1)) # Output: Warning: High temperature detected: 32°C. Turning on the AC. print(process_sensor_data(sensor_data2)) # Output: Warning: High humidity detected: 75%. Activating dehumidifier. print(process_sensor_data(sensor_data3)) # Output: Motion detected in living room. Turning on the lights. print(process_sensor_data(sensor_data4)) # Output: Low light level detected: 30 lux. Turning on the lights.
Нужно пояснить, что мы видим в данном коде. Сначала происходит обработка сообщений, полученных от датчиков температуры:
- Если температура превышает 30°C, система умного дома создает предупреждение и включает кондиционер.
- Если температура соответствует норме, появляется сообщение об этом.
Обработка сообщений от датчиков влажности:
- При превышении показателя влажности 70% нужно включить осушитель и создать об этом сообщение.
- Если влажность соответствует норме, появляется сообщение об этом.
Обработка сообщений от датчиков движения:
- При обнаружении движения в помещении, система включает освещение в указанном месте.
- Если движение не фиксируется, выводится сообщение об отсутствии движения.
Обработка данных от датчиков света:
- При уровне освещенности ниже 50 люкс, система должна включить свет.
- Если уровень освещенности приемлемый, выводится сообщение.
Обработка неизвестных типов сообщений:
- Если сообщение не соответствует ни одному из известных типов, выводится сообщение об ошибке.
Преимущества использования match/case в данном примере:
- Чистота и читабельность кода: благодаря конструкции match/case можно выразить логику обработки сообщений структурно и лаконично.
- Гибкость: используя guards (if), можно изменять как структуру данных, так и их значения.
- Расширяемость: добавление новых типов датчиков и новых условий обработки происходит без существенных изменений кода.
Заключение
Как видите, примеры демонстрируют, что с помощью конструкции match/case можно успешно управлять логикой обработки данных с использованием вложенных структур, условных проверок и рекурсии. Она не только делает код более читаемым, благодаря ей можно также упростить этот код.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: