Конструкция match/case в Python 3.10. Примеры и зачем она нужна
Релиз Python 3.10, вышедший в октябре 2021 года, предложил разработчикам несколько интересных изменений, включая pattern matching statement (оператор сопоставления с шаблонами). Как уверяли авторы PEP 622, на создание этого предложения их вдохновил схожий синтаксис в языках программирования Scala и Erlang.
Если вы еще не знакомы с pattern matching, то у вас сейчас есть хорошая возможность узнать в данной статье, что выполняет эта функция, как происходит совпадение, о предназначении и вариантах использования этого оператора в Python.
Согласно пояснениям, изложенным в 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/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 и выводит, является ли этот день рабочим или выходным.
Опять для начала используем 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, которая позволяет более лаконично сопоставлять значения с их типами.
Сначала применяем 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, которая позволяет легко сопоставлять списки с заданными шаблонами.
Предположим, у нас есть словарь, и мы хотим выполнить определенное действие в зависимости от значений его ключей.
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, которая позволяет легко сопоставлять словари с заданными шаблонами.
Предположим, у нас есть вложенная структура данных — словарь, содержащий список, и мы хотим выполнить определенное действие в зависимости от значений этой структуры.
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. Фрагмент, который вы сейчас увидите, демонстрирует анализ сложной структуры данных и включает сравнение вложенных структур данных, использование условных проверок (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: [...]
Давайте разберем, что мы видим в этом коде.
Предположим, нам поручили разработать систему управления умным домом. Система получает данные от датчиков, установленных в разных помещениях. Они передают информацию о температуре, влажности, уровне освещения и другие данные. Затем все эти сообщения поступают в устройства, которые, скорее всего, имеют разную структуру. Наша задача состоит в том, чтобы эффективно обработать сообщения от датчиков, используя конструкцию match/case в Python.
Согласно условиям задачи для каждого типа датчика нужно:
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.
Нужно пояснить, что мы видим в данном коде. Сначала происходит обработка сообщений, полученных от датчиков температуры:
Обработка сообщений от датчиков влажности:
Обработка сообщений от датчиков движения:
Обработка данных от датчиков света:
Обработка неизвестных типов сообщений:
Преимущества использования match/case в данном примере:
Как видите, примеры демонстрируют, что с помощью конструкции match/case можно успешно управлять логикой обработки данных с использованием вложенных структур, условных проверок и рекурсии. Она не только делает код более читаемым, благодаря ей можно также упростить этот код.
Visual Code от Microsoft, вероятно, один из самых популярных редакторов кода. Разработчики любят его за…
Япония сама по себе — сплошной киберпанк. Это заметил даже культовый писатель жанра Уильям Гибсон,…
Сам по себе телефон Айфон 17 Про Макс – отличный подарок. У него красивая заводская…
На фоне роста спроса на ликвидность в бычьем рынке 2025 года, криптозаймы снова выходят на…
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…