Рубріки: Back-end

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

Останні статті

Комітет зі стандартів C++ відмовився від спроб покращити безпеку мови за зразком Rust

Комітет зі стандартів C++ відмовився від пропозиції щодо створення суворо безпечної підмножини мови, незважаючи на…

17.09.2025

Google представила платіжний протокол для агентів

Google анонсувала запуск Agent Payments Protocol (AP2) — нового протоколу з відкритим кодом, який дозволяє…

17.09.2025

OpenAI обмежить доступ до ChatGPT для неповнолітніх

Генеральний директор OpenAI Сем Альтман пообіцяв суттєво змінити спосіб взаємодії ChatGPT з користувачами віком до…

17.09.2025

Розробник пояснив, чому React вбиває інновації у фронтенді

React, який вважається одним з найпопулярніших JavaScript-фреймворків, вже не перемагає за рахунок своїх технічних переваг.…

16.09.2025

🚀 Ethereum Meetup у Львові!

Ethereum Ukraine вперше приїжджає до Львова – обговорити Ethereum, поспілкуватися й знайти однодумців для нових ідей.…

16.09.2025

VS Code отримав автоматичний вибір моделі. Він надає перевагу Claude 4

Microsoft додає автоматичний вибір LLM-моделі до свого редактора коду Visual Studio Code. Незважаючи на тісну…

16.09.2025