Примеры 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 можно успешно управлять логикой обработки данных с использованием вложенных структур, условных проверок и рекурсии. Она не только делает код более читаемым, благодаря ей можно также упростить этот код.

Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: