Приклади 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: Просте співставлення значень
def day_type(day): match day: case "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday": return "Weekday" case "Saturday" | "Sunday": return "Weekend" case _: return "Invalid day" print(day_type("Saturday")) # Output: Weekend
Приклад 2: Порівняння з типами даних
def describe_value(value): match value: case int(): return "This is an integer." case str(): return "This is a string." case list(): return "This is a list." case _: return "Unknown type." print(describe_value(42)) # Output: This is an integer.
Приклад 3: Співставлення з шаблоном у списку
def process_list(data): match data: case [a, b, c]: return f"List of three elements: {a}, {b}, {c}" case [a, b]: return f"List of two elements: {a}, {b}" case []: return "Empty list" case _: return "Different structure" print(process_list([1, 2, 3])) # Output: List of three elements: 1, 2, 3
Приклад 4: Порівняння з шаблоном у словнику
def handle_request(request): match request: case {"method": "GET", "path": path}: return f"Handle GET request to {path}" case {"method": "POST", "path": path, "data": data}: return f"Handle POST request to {path} with data: {data}" case _: return "Unknown request format" print(handle_request({"method": "POST", "path": "/submit", "data": {"key": "value"}})) # Output: Handle POST request to /submit with data: {'key': 'value'}
Приклад 5: Співставлення вкладених структур даних
def parse_response(response): match response: case {"status": 200, "data": {"id": id, "name": name}}: return f"Success! ID: {id}, Name: {name}" case {"status": 404}: return "Not Found" case {"status": 500, "error": error}: return f"Server Error: {error}" case _: return "Unknown response format" print(parse_response({"status": 200, "data": {"id": 123, "name": "Alice"}})) # Output: Success! ID: 123, Name: Alice
Складний приклад коду з використанням 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 можна успішно керувати логікою обробки даних з використанням вкладених структур, умовних перевірок та рекурсії. Вона не тільки робить код більш читабельним, завдяки їй можна також його значно спростити.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: