Форум программистов, компьютерный форум, киберфорум
py-thonny
Войти
Регистрация
Восстановить пароль

Сопоставление с образцом (Pattern Matching) в Python: Списки и словари

Запись от py-thonny размещена 19.03.2025 в 13:33
Показов 1500 Комментарии 0
Метки pattern matching, python

Нажмите на изображение для увеличения
Название: bbe7c266-9613-4f9d-ae09-71fc772c66db.jpg
Просмотров: 90
Размер:	200.2 Кб
ID:	10460
Программисты любят, когда код говорит сам за себя. Представьте, что вы можете просмотреть структуру данных и мгновенно понять, что с ней делать — без сложных условий и вложенных проверок. Именно эту элегантность предлагает паттерн-матчинг, который появился в Python 3.10 и стал значительным шагом в эволюции языка.

Паттерн-матчинг — это способность программы анализировать структуру данных и выполнять действия в зависимости от её формы. По сути, это как если бы вы могли сказать: "Если данные выглядят так, сделай это; если иначе — сделай то". Звучит просто, но в этой простоте и кроется вся сила. В отличие от традиционных конструкций if-elif-else, которые проверяют значения, паттерн-матчинг фокусируется на форме и структуре. Это ключевое различие делает код не только более читаемым, но и значительно упрощает работу со сложными структурами данных, такими как списки и словари.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Традиционный подход с условными операторами
def process_data(data):
    if isinstance(data, list):
        if len(data) > 0 and data[0] == "command":
            if len(data) > 1:
                # Обработка команды с аргументами
                pass
            else:
                # Обработка команды без аргументов
                pass
    elif isinstance(data, dict):
        if "type" in data and data["type"] == "message":
            # Обработка сообщения
            pass
    
# С использованием паттерн-матчинга
def process_data(data):
    match data:
        case ["command", *args] if args:
            # Обработка команды с аргументами
            pass
        case ["command"]:
            # Обработка команды без аргументов
            pass
        case {"type": "message"}:
            # Обработка сообщения
            pass
Взглянув на пример выше, сразу видно, насколько более выразительным становится код с паттерн-матчингом. Он не только короче, но и проще для понимания — структура данных сразу видна в шаблоне.

История конструкции match-case в Python интересна тем, что язык долго шел к ней. Многие языки программирования, особенно из функционального семейства, давно имели такую возможность. Haskell и Scala известны своим мощным паттерн-матчингом, Rust сделал его центральным элементом работы с данными. Python с его философией читаемости кода не мог оставаться в стороне.

В Python 3.10, выпущенном в октябре 2021 года, паттерн-матчинг наконец стал реальностью. Это было результатом долгих обсуждений и разработки через PEP 634, 635 и 636 (Python Enhancement Proposals). Новая конструкция получилась не просто заимствованием из других языков, а самостоятельным решением, отражающим дух Python — простой, но мощный инструмент. Особенная красота паттерн-матчинга раскрывается при работе с данными, имеющими сложную структуру — многоуровневыми списками, вложенными словарями, комбинациями различных типов. Это та область, где традиционные условные выражения становятся громоздкими и трудночитаемыми, а новая конструкция раскрывает все свои преимущества.

Основы работы с паттерн-матчингом



Синтаксис паттерн-матчинга в Python элегантен и интуитивно понятен. Основная конструкция состоит из двух ключевых слов: match и case. Выражение match принимает объект для сопоставления, а блоки case определяют шаблоны и соответствующие им действия.

Python
1
2
3
4
5
6
7
match значение:
    case шаблон_1:
        # код, выполняемый при соответствии шаблону_1
    case шаблон_2:
        # код, выполняемый при соответствии шаблону_2
    case _:
        # код для всех остальных случаев (аналог default)
При первом взгляде паттерн-матчинг может показаться просто аналогом конструкции switch-case из других языков, но это поверхностное сравнение. Фундаментальное отличие заключается в том, что switch-case обычно работает только с простыми значениями (числами, строками, перечислениями), тогда как паттерн-матчинг в Python способен анализировать структуру данных и их содержимое. Рассмотрим простой пример:

Python
1
2
3
4
5
6
7
8
9
10
11
12
def describe_value(x):
    match x:
        case 0:
            return "Ноль"
        case 1:
            return "Один"
        case _:
            return f"Число {x}"
 
print(describe_value(0))  # Ноль
print(describe_value(1))  # Один
print(describe_value(5))  # Число 5
Здесь шаблоны достаточно просты — мы сопоставляем значения с конкретными числами. Но настоящее могущество паттерн-матчинга проявляется, когда мы начинаем использовать структурные шаблоны:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def analyze_point(point):
    match point:
        case (0, 0):
            return "Начало координат"
        case (0, y):
            return f"Точка на оси Y: {y}"
        case (x, 0):
            return f"Точка на оси X: {x}"
        case (x, y) if x == y:
            return f"Точка на диагонали: {x}, {y}"
        case (x, y):
            return f"Точка в пространстве: {x}, {y}"
 
print(analyze_point((0, 0)))       # Начало координат
print(analyze_point((0, 5)))       # Точка на оси Y: 5
print(analyze_point((3, 3)))       # Точка на диагонали: 3, 3
print(analyze_point((2, 4)))       # Точка в пространстве: 2, 4
В этом примере мы уже видим несколько важных концепций паттерн-матчинга:
1. Сопоставление с конкретными значениямиcase (0, 0) проверяет, равна ли точка началу координат.
2. Захват значенийcase (0, y) захватывает значение координаты Y, если X равен нулю.
3. Защитные выражения (guards)case (x, y) if x == y добавляет дополнительное условие после основного шаблона.

Защитные выражения — это мощный инструмент, который позволяет уточнить шаблон с помощью произвольного логического условия. Они начинаются с ключевого слова if после шаблона и могут содержать любое выражение, возвращающее булево значение.

Python
1
2
3
4
5
6
7
8
9
10
11
12
def process_age(person):
    match person:
        case {"name": name, "age": age} if age < 18:
            return f"{name} — несовершеннолетний"
        case {"name": name, "age": age} if age >= 65:
            return f"{name} — пенсионер"
        case {"name": name, "age": age}:
            return f"{name} — взрослый"
 
print(process_age({"name": "Алиса", "age": 15}))    # Алиса — несовершеннолетний
print(process_age({"name": "Боб", "age": 35}))      # Боб — взрослый
print(process_age({"name": "Карл", "age": 70}))     # Карл — пенсионер
Важно понимать порядок выполнения проверок в паттерн-матчинге. Python проверяет шаблоны последовательно, сверху вниз, и выполняет код для первого совпадения. Как только найдено соответствие, выполнение блока match завершается. Это значит, что порядок шаблонов имеет значение — более специфичные шаблоны должны идти перед более общими.

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
# Правильный порядок — от специфичного к общему
match value:
    case [1, 2]:         # сначала специфичный шаблон
        print("Список [1, 2]")
    case [int(), int()]: # затем более общий
        print("Список из двух целых чисел")
 
# Неправильный порядок — общий шаблон перехватит все совпадения
match value:
    case [int(), int()]:   # этот шаблон уже перехватит [1, 2]
        print("Список из двух целых чисел")
    case [1, 2]:           # этот шаблон никогда не будет достигнут
        print("Список [1, 2]")
В отличие от конструкции switch-case во многих других языках, в Python не требуется явно прерывать выполнение с помощью break или подобных операторов. Это устраняет распространенный источник ошибок, когда программисты забывают добавить break и код "проваливается" в следующий блок.

Вместе с тем, паттерн-матчинг в Python предоставляет механизмы, которых нет в традиционных switch-case:

1. Сопоставление с подтипами — можно проверять, является ли объект экземпляром определённого класса:

Python
1
2
3
4
5
6
7
8
def process_shape(shape):
    match shape:
        case Circle(radius=r):
            return f"Круг с радиусом {r}"
        case Rectangle(width=w, height=h):
            return f"Прямоугольник {w}×{h}"
        case _:
            return "Неизвестная фигура"
2. OR-шаблоны — позволяют объединять несколько шаблонов:

Python
1
2
3
4
5
match command:
    case "quit" | "exit" | "bye":
        exit_program()
    case "help" | "?":
        show_help()
3. Деструктуризация объектов — позволяет извлекать отдельные атрибуты объектов:

Python
1
2
3
4
5
6
7
8
9
10
11
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
def analyze(point):
    match point:
        case Point(x=0, y=0):
            return "В начале координат"
        case Point(x=0, y=y):
            return f"На оси Y: {y}"
Важный аспект паттерн-матчинга в Python — его интеграция с системой типов. Хотя Python — динамически типизированный язык, паттерн-матчинг фактически вводит элементы структурной типизации, где важна не только конкретная реализация объекта, но и наличие определённых атрибутов и методов.

Python
1
2
3
4
5
6
7
# Пример использования структурной типизации в паттерн-матчинге
def describe_sequence(seq):
    match seq:
        case seq if hasattr(seq, "__len__"):
            return f"Последовательность длиной {len(seq)}"
        case _:
            return "Не последовательность"
Чтобы избежать ошибок при работе с паттерн-матчингом, полезно знать несколько особенностей:
1. Переменная с именем _ в шаблоне действует как подстановочный знак и не связывается ни с каким значением.
2. Имена переменных в разных частях одного шаблона должны иметь одинаковые значения в соответствующих местах проверяемого объекта.
3. Чтобы использовать переменную из внешней области видимости в шаблоне, её нужно тексутально заключить в кавычки.

Python
1
2
3
4
x = 1
match value:
    case `x`:  # проверит равенство с переменной x из внешней области
        print("Совпадает с x")
При обработке логических условий паттерн-матчинг может заменить сложные конструкции из вложенных условных выражений, делая код более линейным и понятным. Особенно это проявляется при анализе состояний в играх, парсинге командной строки или обработке событий в интерфейсе.

Списки и словари Python
Здраствуйте, подскажите а можно ли как-то достать из списка в котором словарь с ключами, значения ключей выборочно и одним кодом? если у меня там...

Python: списки, множества, словари (основы)
. Напишите программу, на вход которой подается строка из целых чисел, разделенных пробелом. Необходимо вывести произведение введенных чисел, если...

Pattern matching
здравствуйте, как известно: С релизом python 3.10 появился pattern matching, собственно и являющийся главной особенностью этого релиза. Возможно...

Списки.Словари
. На балансе предприятия находится десять зданий. Для каждого из них известна начальная стоимость (руб) и величина износа (%). Определить ...


Матчинг списков



Списки — одна из самых фундаментальных структур данных в Python, и паттерн-матчинг раскрывает новые горизонты в работе с ними. Структурное сопоставление позволяет безошибочно декомпозировать список на составные элементы и обрабатывать его содержимое элегантным способом.
Самая базовая форма матчинга списков — это сопоставление с конкретным списком, содержащим определённые значения:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def process_command(cmd):
    match cmd:
        case ["help"]:
            return "Показываю справку"
        case ["exit"]:
            return "Выхожу из программы"
        case ["version"]:
            return "Версия 1.0"
        case _:
            return "Неизвестная команда"
 
print(process_command(["help"]))     # Показываю справку
print(process_command(["version"]))  # Версия 1.0
print(process_command(["status"]))   # Неизвестная команда
В этом примере мы сопоставляем входную команду с конкретными шаблонами. Однако списки часто имеют более сложную структуру и переменную длину. Здесь на помощь приходят расширенные возможности паттерн-матчинга.
Оператор звёздочки (*) позволяет захватывать произвольное количество элементов. Это невероятно удобно при работе с последовательностями неизвестной длины:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def analyze_data(data):
    match data:
        case []:
            return "Пустой список"
        case [x]:
            return f"Список с одним элементом: {x}"
        case [x, y]:
            return f"Пара элементов: {x}, {y}"
        case [x, y, *rest]:
            return f"Длинный список. Первые два: {x}, {y}. Остальных: {len(rest)}"
 
print(analyze_data([]))                   # Пустой список
print(analyze_data([42]))                 # Список с одним элементом: 42
print(analyze_data([1, 2]))               # Пара элементов: 1, 2
print(analyze_data([1, 2, 3, 4, 5]))      # Длинный список. Первые два: 1, 2. Остальных: 3
Оператор * может находиться не только в конце шаблона, но и в середине, что позволяет создавать более сложные шаблоны:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def process_command(args):
    match args:
        case ["count", *files, "--verbose"]:
            return f"Подсчитываю файлы {files} с подробным выводом"
        case ["count", *files]:
            return f"Подсчитываю файлы {files}"
        case [cmd, *_] if cmd not in ["count", "list", "delete"]:
            return f"Неизвестная команда: {cmd}"
 
print(process_command(["count", "file1.txt", "file2.txt"]))
[H2]Подсчитываю файлы ['file1.txt', 'file2.txt'][/H2]
 
print(process_command(["count", "file1.txt", "--verbose"]))
# Подсчитываю файлы ['file1.txt'] с подробным выводом
Важный аспект работы с оператором * — он собирает элементы в список, даже если совпадений нет. В последнем случае получается пустой список:

Python
1
2
3
4
5
6
7
8
9
match [1, 2, 3]:
    case [first, *middle, last]:
        print(f"Первый: {first}, середина: {middle}, последний: {last}")
[H2]Вывод: Первый: 1, середина: [2], последний: 3[/H2]
 
match [1, 2]:
    case [first, *middle, last]:
        print(f"Первый: {first}, середина: {middle}, последний: {last}")
# Вывод: Первый: 1, середина: [], последний: 2
При работе со списками можно комбинировать литеральные значения, переменные и сложные выражения в рамках одного шаблона. Это делает паттерн-матчинг гибким инструментом для обработки сложных структур:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def process_command(cmd):
    match cmd:
        case ["set", name, value] if name.startswith("--"):
            return f"Устанавливаю опцию {name[2:]} = {value}"
        case ["set", name, value]:
            return f"Устанавливаю переменную {name} = {value}"
        case ["get", *variables] if variables:
            return f"Получаю значения переменных: {', '.join(variables)}"
        case ["get"]:
            return "Ошибка: не указаны переменные для получения"
 
print(process_command(["set", "--verbose", "true"]))  
[H2]Устанавливаю опцию verbose = true[/H2]
 
print(process_command(["get", "name", "age", "address"]))  
# Получаю значения переменных: name, age, address
Одна из самых мощных возможностей при работе с матчингом списков — рекурсивное сопоставление вложенных структур. Вы можете задавать шаблоны для элементов внутри списка, создавая сложные конструкции сопоставления:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def process_nested_data(data):
    match data:
        case [subject, [grade1, grade2, *_]]:
            return f"Предмет {subject} имеет оценки {grade1} и {grade2}"
        case [subject, []]:
            return f"Предмет {subject} не имеет оценок"
        case [subject, grades] if isinstance(grades, list):
            return f"Предмет {subject} имеет {len(grades)} оценок"
        case [name, address, [phone, *alt_phones]]:
            return f"Контакт {name}: {address}, тел. {phone}"
 
print(process_nested_data(["Математика", [5, 4, 3, 5]]))
[H2]Предмет Математика имеет оценки 5 и 4[/H2]
 
print(process_nested_data(["Иван Иванов", "ул. Пушкина, 10", ["123-45-67", "987-65-43"]]))
# Контакт Иван Иванов: ул. Пушкина, 10, тел. 123-45-67
Подстановочный знак (_) часто используется, когда нам нужно только проверить структуру, но не захватывать конкретные значения:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def validate_format(data):
    match data:
        case [_, _, _]:
            return "Список из трёх элементов"
        case [[_, _], [_, _]]:
            return "Список из двух пар"
        case [{"id": _}, {"id": _}]:
            return "Два объекта с ID"
        case _:
            return "Неизвестный формат"
 
print(validate_format([1, 2, 3]))               # Список из трёх элементов
print(validate_format([[1, 2], [3, 4]]))        # Список из двух пар
print(validate_format([{"id": 1}, {"id": 2}]))  # Два объекта с ID
При работе со списками можно использовать OR-шаблоны (с помощью оператора |) для обработки альтернативных вариантов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def classify_command(cmd):
    match cmd:
        case ["add" | "create" | "new", name]:
            return f"Создание: {name}"
        case ["delete" | "remove", name]:
            return f"Удаление: {name}"
        case ["show" | "display" | "view", name]:
            return f"Просмотр: {name}"
        case _:
            return "Неизвестная команда"
 
print(classify_command(["add", "user"]))    # Создание: user
print(classify_command(["remove", "file"]))  # Удаление: file
print(classify_command(["view", "log"]))     # Просмотр: log
В реальных приложениях часто встречаются сложные команды с множеством опций. Рассмотрим пример процессинга команды:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from enum import Enum
 
class FileType(Enum):
    SOURCE_CODE = "src"
    HTML = "html"
    IMAGES = "img"
    DOCUMENTS = "doc"
    OTHERS = "other"
 
def process_command(args):
    match args:
        case ["count", (FileType.SOURCE_CODE.value | FileType.HTML.value | FileType.IMAGES.value | FileType.DOCUMENTS.value | FileType.OTHERS.value) as file_type]:
            return f"Подсчёт файлов типа {file_type}"
        
        case ["count", _, *_]:
            return "Ошибка: команда 'count' требует точно один аргумент с типом файла"
        
        case ["sha1", "verify"] | ["sha1", "verify", _, _, _, *_]:
            return "Ошибка: команда 'verify-sha1' требует ровно два аргумента (путь к файлу и ожидаемый хеш)"
        
        case ["sha1", "verify", file_path, expected_hash]:
            return f"Проверка SHA1 для файла {file_path}"
        
        case ["sha1", *file_paths] if file_paths:
            return f"Расчёт SHA1 для файлов: {', '.join(file_paths)}"
        
        case _:
            return "Неизвестная команда"
В этом примере мы видим несколько важных техник:
1. Использование перечислений Enum для проверки допустимых значений.
2. Захват значения, совпадающего с одним из вариантов, с помощью оператора as.
3. Проверка количества аргументов через шаблоны с подстановочными знаками.
4. Комбинирование шаблонов с помощью OR-оператора (|).
5. Использование защитных условий для валидации списка.

Матчинг списков особенно полезен при обработке аргументов командной строки, анализе данных в формате JSON, и при работе с API, которые возвращают структурированные данные. Это удобный инструмент для программ, которые должны интерпретировать текстовые команды от пользователя или запросы от клиентов.

При работе с коллекциями данных мы можем комбинировать паттерн-матчинг с проверкой типов для более точного контроля над обработкой данных. Это особенно удобно, когда нам нужно различать списки разных составов:

Python
1
2
3
4
5
6
7
8
9
10
def process_data(data):
match data:
    case [str(title), int(year)] if 1900 <= year <= 2023:
        return f"Фильм: {title} ({year})"
    case [str(name), float(price)]:
        return f"Товар: {name}, цена: {price}"
    case [int(x), int(y), int(z)]:
        return f"3D координаты: ({x}, {y}, {z})"
    case [*items] if all(isinstance(x, int) for x in items):
        return f"Список целых чисел: {sum(items)}"
Обратим внимание на последний шаблон — он показывает, как можно комбинировать паттерн-матчинг с классическими функциями Python для выполнения более сложных проверок.
Ещё одна сильная сторона матчинга списков — возможность захвата фиксированных позиций в комбинации со звёздочным оператором:

Python
1
2
3
4
5
6
7
8
def parse_log_entry(entry):
match entry:
    case [timestamp, "ERROR", message, *details]:
        return f"Критическая ошибка в {timestamp}: {message}"
    case [timestamp, "WARNING", message, *_]:
        return f"Предупреждение в {timestamp}: {message}"
    case [timestamp, level, *_]:
        return f"Запись уровня {level} в {timestamp}"
При работе с вложенными структурами данных можно создавать очень детальные шаблоны:

Python
1
2
3
4
5
6
7
8
9
10
def analyze_student_data(data):
match data:
    case [name, [grade1, grade2]] if grade1 > 90 and grade2 > 90:
        return f"Отличник: {name}"
    case [name, [_, _, *rest]] if len(rest) > 5:
        return f"Много оценок у {name}"
    case [name, grades] if sum(grades) / len(grades) >= 70:
        return f"Хороший студент: {name}"
    case [name, _]:
        return f"Требует внимания: {name}"
Особенно удобен матчинг списков при обработке результатов парсинга, где разные структуры данных могут представлять разные типы элементов:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
def render_html_element(element):
match element:
    case ["img", {"src": src, "alt": alt, **attrs}]:
        extra = " ".join(f'{k}="{v}"' for k, v in attrs.items())
        return f'<img src="{src}" alt="{alt}" {extra}>'
    case ["a", {"href": href}, *content]:
        inner = "".join(render_html_element(item) if isinstance(item, list) else str(item) for item in content)
        return f'<a href="{href}">{inner}</a>'
    case ["div", *content]:
        inner = "".join(render_html_element(item) if isinstance(item, list) else str(item) for item in content)
        return f'<div>{inner}</div>'
    case str(text):
        return text
Эти примеры иллюстрируют, как паттерн-матчинг списков позволяет создавать элегантный код для обработки сложных структур данных. Вместо вложенных условных операторов и проверок типов, мы получаем декларативный стиль программирования, где структура шаблонов отражает структуру обрабатываемых данных.

Не стоит забывать про кортежи — они ведут себя похоже на списки в контексте паттерн-матчинга, но с важным отличием: шаблоны для кортежей требуют, чтобы входные данные были именно кортежами, а не списками, даже если элементы идентичны.

Python
1
2
3
4
5
6
7
8
9
# Этот шаблон сработает только для кортежа
match value:
case (1, 2):
    print("Кортеж (1, 2)")
 
# Этот шаблон сработает только для списка
match value:
case [1, 2]:
    print("Список [1, 2]")
Когда мы используем захват с помощью * в кортежных шаблонах, захваченные элементы также собираются в кортеж, а не в список. Это важно помнить при дальнейшей обработке данных.

Матчинг словарей



Словарь (dict) — одна из ключевых структур данных в Python, используемая для хранения пар "ключ-значение". Словари незаменимы при работе с JSON-данными, конфигурациями, человеко-читаемыми настройками и многими другими форматами. Паттерн-матчинг превращает обработку словарей из рутинного занятия в изящное и выразительное действо. В отличие от матчинга списков, где порядок элементов имеет значение, при сопоставлении с образцом словарей важны только ключи и их значения, но не порядок их определения. Это полностью соответствует природе словарей как неупорядоченных коллекций.
Базовый синтаксис для матчинга словарей выглядит следующим образом:

Python
1
2
3
4
5
6
7
8
9
10
def process_user_data(user_data):
match user_data:
    case {"name": name, "age": age}:
        return f"Пользователь {name}, возраст {age}"
    case {"username": username}:
        return f"Пользователь с именем {username}"
    case {"email": email}:
        return f"Контакт: {email}"
    case _:
        return "Неизвестный формат данных"
Когда вы сопоставляете словарь с шаблоном, Python проверяет наличие указанных ключей и сопоставляет их значения. Есть важная особенность: шаблон словаря по умолчанию проверяет только наличие указанных ключей. Если в словаре присутствуют дополнительные ключи, они игнорируются:

Python
1
2
3
4
5
6
data = {"name": "Алексей", "age": 28, "city": "Москва", "job": "программист"}
 
match data:
case {"name": name, "age": age}:
    print(f"{name}, {age} лет")
# Выведет: "Алексей, 28 лет", несмотря на наличие дополнительных ключей
Это поведение называется "частичным сопоставлением" (partial matching) и делает матчинг словарей гибким инструментом при работе с разнородными данными. Вы можете сосредоточиться только на тех ключах, которые вам действительно нужны для обработки. Если вам нужно захватить весь словарь, соответствующий шаблону, используйте оператор as:

Python
1
2
3
4
5
match data:
case {"name": name, "age": age} as person:
    print(f"Имя: {name}, возраст: {age}")
    print(f"Полные данные: {person}")
# person будет содержать исходный словарь
Паттерн-матчинг позволяет проверять вложенные словари, создавая сложные структурные шаблоны:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def process_order(order):
match order:
    case {"customer": {"name": name, "id": id}, "items": items}:
        return f"Заказ от {name} (ID: {id}) содержит {len(items)} товаров"
    case {"customer": {"company": company}, "total": total}:
        return f"Корпоративный заказ от {company} на сумму {total}"
    case {"status": "pending", "payment": payment}:
        return f"Ожидается оплата: {payment}"
    case {"status": status} if status in ["shipped", "delivered"]:
        return f"Заказ в пути или доставлен, статус: {status}"
 
# Примеры вызовов
print(process_order({"customer": {"name": "Иван", "id": 12345}, "items": ["Книга", "Ручка"]}))
[H2]Заказ от Иван (ID: 12345) содержит 2 товаров[/H2]
 
print(process_order({"customer": {"company": "ООО Рога и Копыта"}, "total": 15000}))
# Корпоративный заказ от ООО Рога и Копыта на сумму 15000
В отличие от списков, в шаблонах словарей нельзя использовать оператор звёздочки (*) для сбора неуказанных ключей. Однако, можно использовать оператор распаковки словаря (**) для захвата дополнительных ключей:

Python
1
2
3
4
5
6
7
8
def analyze_config(config):
match config:
    case {"host": host, "port": port, **rest}:
        extra = ", ".join(f"{k}={v}" for k, v in rest.items())
        return f"Соединение с {host}:{port}" + (f" (доп. параметры: {extra})" if rest else "")
 
print(analyze_config({"host": "localhost", "port": 8080, "debug": True, "timeout": 30}))
# Соединение с localhost:8080 (доп. параметры: debug=True, timeout=30)
Особенно полезно сочетание матчинга словарей и защитных условий (guards), которые позволяют добавлять дополнительную логику проверки:

Python
1
2
3
4
5
6
7
8
9
10
11
12
def process_event(event):
match event:
    case {"type": "click", "position": (x, y)} if 0 <= x < 100 and 0 <= y < 100:
        return f"Клик в левом верхнем квадранте: ({x}, {y})"
    case {"type": "click", "position": (x, y)}:
        return f"Клик за пределами левого верхнего квадранта: ({x}, {y})"
    case {"type": "hover", "duration": duration} if duration > 5:
        return f"Длительное наведение: {duration} секунд"
    case {"type": event_type, "timestamp": timestamp} if event_type not in ["click", "hover"]:
        return f"Необычное событие {event_type} в {timestamp}"
    case _:
        return "Неизвестное событие"
В этом примере мы не только проверяем наличие определённых ключей, но и анализируем значения с помощью дополнительных условий.
Интересная особенность матчинга словарей — возможность комбинировать проверки ключей и проверки типов значений:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def validate_user_input(data):
match data:
    case {"age": int(age)} if age >= 18:
        return "Совершеннолетний пользователь"
    case {"age": int(age)}:
        return f"Несовершеннолетний пользователь, возраст: {age}"
    case {"age": value}:
        return f"Некорректный формат возраста: {value}"
    case {"name": str(name)} if len(name.strip()) > 2:
        return f"Пользователь {name}"
    case {"name": _}:
        return "Некорректное имя"
    case _:
        return "Отсутствует обязательная информация"
Для обработки API-ответов и JSON-структур, матчинг словарей предоставляет мощный инструментарий. Рассмотрим пример обработки ответа от REST API:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def handle_api_response(response):
match response:
    case {"status": "success", "data": data}:
        # Обработка успешного ответа с данными
        return process_data(data)
    case {"status": "success", "message": message}:
        # Успешный ответ с информационным сообщением
        return f"Операция выполнена успешно: {message}"
    case {"status": "error", "error": {"code": code, "message": message}}:
        # Обработка ошибки с кодом и сообщением
        return f"Ошибка {code}: {message}"
    case {"status": "error", "message": message}:
        # Обработка простой ошибки
        return f"Произошла ошибка: {message}"
    case {"meta": meta, **data} if "status" not in data:
        # Ответ без статуса, но с метаданными
        return f"Получены данные с метаинформацией: {meta}"
    case _:
        # Необработанный формат ответа
        return "Неизвестный формат ответа от API"
 
# Пример вызова
response = {
    "status": "error",
    "error": {
        "code": 404,
        "message": "Ресурс не найден"
    },
    "request_id": "a1b2c3d4"
}
print(handle_api_response(response))
# Ошибка 404: Ресурс не найден
При работе с конфигурациями часто требуется установить значения по умолчанию для отсутствующих параметров. Паттерн-матчинг может упростить и эту задачу:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def apply_config(config):
defaults = {
    "debug": False,
    "timeout": 30,
    "retries": 3,
    "log_level": "INFO"
}
 
match config:
    case {"environment": "production", [B]settings}:
        # В производственном окружении принудительно отключаем отладку
        return {[/B]defaults, **settings, "debug": False, "log_level": "WARNING"}
    case {"environment": "development", [B]settings}:
        # В среде разработки включаем отладку и подробное логирование
        return {[/B]defaults, **settings, "debug": True, "log_level": "DEBUG"}
    case {"environment": env, **settings} if env in ["testing", "staging"]:
        # Для тестовых сред
        return {**defaults, **settings, "debug": settings.get("debug", True)}
    case settings:
        # Если окружение не указано, применяем настройки как есть
        return {**defaults, **settings}
Для валидации форматов данных, паттерн-матчинг предоставляет элегантное решение:

Python
1
2
3
4
5
6
7
8
9
10
def validate_address(address):
match address:
    case {"country": "Россия", "postal_code": code} if len(code) == 6 and code.isdigit():
        return True
    case {"country": "США", "zip_code": code} if (len(code) == 5 or len(code) == 10) and "-" in code:
        return True
    case {"country": country, **_} if country not in ["Россия", "США"]:
        return "Неподдерживаемая страна"
    case _:
        return False
Паттерн-матчинг словарей особенно полезен при работе с данными, которые имеют различные форматы или версии. Допустим, вы обрабатываете события из разных источников, каждый из которых имеет свою структуру:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def normalize_event(event):
match event:
case {"event_type": type, "timestamp": ts, "data": data}:
    # Современный формат
    return {"type": type, "time": ts, "payload": data}
case {"type": type, "time": ts, "payload": payload}:
    # Уже нормализованный формат
    return {"type": type, "time": ts, "payload": payload}
case {"action": action, "when": ts, **data}:
    # Устаревший формат
    return {"type": action, "time": ts, "payload": data}
case {"message": msg, "sent_at": ts}:
    # Формат сообщений
    return {"type": "message", "time": ts, "payload": {"text": msg}}
case _:
    raise ValueError("Неизвестный формат события")
Еще одним преимуществом матчинга словарей является возможность комбинировать его с другими видами паттернов. Это позволяет создавать гибкие системы обработки структурированных данных, например, при анализе результатов запросов к базам данных или обработке сообщений из очередей:

Python
1
2
3
4
5
6
7
8
9
10
11
def process_database_result(result):
    match result:
        case {"rows": [{"id": id, "name": name, **fields}], "count": 1}:
            return f"Найдена одна запись: {name} (ID: {id})"
        case {"rows": rows, "count": count} if count > 1:
            names = [row.get("name", f"Запись {row['id']}") for row in rows if "id" in row]
            return f"Найдено {count} записей: {', '.join(names)}"
        case {"rows": [], "count": 0}:
            return "Записи не найдены"
        case {"error": {"message": message}}:
            return f"Ошибка базы данных: {message}"
При работе с вложенными структурами в JSON-данных, паттерн-матчинг может существенно упростить извлечение релевантной информации:

Python
1
2
3
4
5
6
7
8
9
10
def extract_weather_data(data):
    match data:
        case {"current": {"temp_c": temp, "condition": {"text": condition}}, 
              "location": {"name": city, "country": country}}:
            return f"В городе {city} ({country}) сейчас {temp}°C, {condition.lower()}"
        case {"forecast": {"forecastday": [{"date": date, "day": {"maxtemp_c": max_temp, 
                                                                 "mintemp_c": min_temp}}]}}:
            return f"Прогноз на {date}: от {min_temp}°C до {max_temp}°C"
        case {"error": error}:
            return f"Не удалось получить данные: {error.get('message', 'Неизвестная ошибка')}"
Вместо традиционных глубоко вложенных проверок можно определить шаблоны, соответствующие конкретным схемам данных:

Python
1
2
3
4
5
6
7
8
9
10
def process_payment_info(payment):
    match payment:
        case {"status": "pending", "method": "card", "card": {"last4": digits, "expire": expire}}:
            return f"Ожидание подтверждения карты *{digits} (до {expire})"
        case {"status": "completed", "amount": amount, "currency": currency}:
            return f"Оплата на сумму {amount} {currency} выполнена"
        case {"status": "failed", "error": error, "retry_allowed": True}:
            return f"Ошибка оплаты: {error}. Можно повторить."
        case {"status": status} if status not in ["pending", "completed", "failed"]:
            return f"Необычный статус платежа: {status}"
Производительность паттерн-матчинга со словарями в Python оптимизирована и сравнима с традиционными подходами проверки ключей. В большей части случаев разница в производительности между использованием match-case и цепочки if-elif-else с проверкой ключей незначительна. Основное преимущество состоит в улучшении читаемости и уменьшении вероятности ошибок, особенно при работе со сложными структурами. Однако, стоит помнить о нескольких особенностях. Если в шаблоне вы указываете большое количество ключей (более 10-15), каждый из которых требует проверки, то это может привести к некоторому снижению производительности по сравнению с оптимизированным кодом, который проверяет только необходимые ключи. Но на практике такие случаи редки, а преимущества в удобочитаемости и сопровождаемости кода обычно превышают потенциальные незначительные потери в скорости.

При работе с API и внешними источниками данных полезно добавлять защитные условия для проверки типов данных:

Python
1
2
3
4
5
6
7
8
9
10
def validate_user(user_data):
    match user_data:
        case {"id": id, "email": email, "roles": roles} if isinstance(id, int) and isinstance(roles, list):
            return f"Действительный пользователь: {email}"
        case {"id": _, "email": email} if "@" not in email:
            return f"Некорректный email: {email}"
        case {"temp_access": True, "expires_at": expires} if isinstance(expires, (int, float)):
            return f"Временный доступ до {expires}"
        case _:
            return "Неполные или некорректные данные пользователя"
Сравнивая паттерн-матчинг словарей с традиционными подходами, можно увидеть существенное улучшение структуры кода:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Традиционный подход
def process_response_old(response):
    if "error" in response:
        if "code" in response["error"]:
            code = response["error"]["code"]
            message = response["error"].get("message", "Неизвестная ошибка")
            return f"Ошибка {code}: {message}"
        else:
            return f"Ошибка: {response['error']}"
    elif "data" in response:
        if "items" in response["data"]:
            items = response["data"]["items"]
            return f"Получено {len(items)} элементов"
        else:
            return "Данные без элементов"
    else:
        return "Неизвестный формат ответа"
 
# С использованием паттерн-матчинга
def process_response_new(response):
    match response:
        case {"error": {"code": code, "message": message}}:
            return f"Ошибка {code}: {message}"
        case {"error": error}:
            return f"Ошибка: {error}"
        case {"data": {"items": items}}:
            return f"Получено {len(items)} элементов"
        case {"data": {}}:
            return "Данные без элементов"
        case _:
            return "Неизвестный формат ответа"
Очевидно, что второй вариант значительно более читабельный, хорошо структурированный и меньше подвержен ошибкам.

При работе со сложными вложенными структурами данных, например, при обработке результатов работы REST API или GraphQL, паттерн-матчинг может существенно упростить выделение нужной информации:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
def extract_product_details(product_data):
    match product_data:
        case {"product": {"name": name, "price": {"current": price}, "availability": "in_stock"}}:
            return f"{name} доступен по цене {price}"
        case {"product": {"name": name, "price": {"current": price, "old": old_price}, "discount": discount}}:
            savings = old_price - price
            return f"{name} со скидкой {discount}%: {price} (экономия {savings})"
        case {"product": {"name": name, "availability": "out_of_stock", "expected_date": date}}:
            return f"{name} временно отсутствует, ожидается {date}"
        case {"product": {"name": name, "availability": "out_of_stock"}}:
            return f"{name} отсутствует в продаже"
        case {"error": error}:
            return f"Не удалось получить информацию о товаре: {error}"
Для обработки конфигурационных файлов в форматах JSON или YAML, паттерн-матчинг обеспечивает декларативный способ проверки структуры и извлечения нужных параметров:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def configure_app(config):
    match config:
        case {"app": {"name": name, "version": version}, "logging": {"level": level}}:
            setup_app(name, version)
            setup_logging(level)
            return f"Приложение {name} v{version} настроено с уровнем логирования {level}"
        case {"app": app_config, "database": db_config, "cache": cache_config}:
            setup_app([B]app_config)
            setup_database([/B]db_config)
            setup_cache([B]cache_config)
            return "Приложение настроено с базой данных и кешем"
        case {"app": app_config}:
            setup_app([/B]app_config)
            return "Настроено только приложение, без дополнительных сервисов"
        case _:
            raise ValueError("Неверная конфигурация")
Матчинг словарей предоставляет элегантное решение для обработки различных версий API или форматов данных, что особенно полезно при миграции с одной версии на другую:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
def process_api_data(data, version=None):
    match data:
        case {"api_version": "v1", "user": {"full_name": name, "email": email}}:
            # Обработка данных для API v1
            return create_user_v1(name, email)
        case {"api_version": "v2", "user": {"first_name": first, "last_name": last, "contact": {"email": email}}}:
            # Обработка данных для API v2
            return create_user_v2(first, last, email)
        case {"user": user_data} if version == "v1":
            # Данные без указания версии, но версия передана в параметрах
            return create_user_v1(user_data.get("full_name"), user_data.get("email"))
        case _:
            raise ValueError("Неподдерживаемый формат данных API")

Практические примеры



Теория хороша, но практическое применение показывает истинную силу паттерн-матчинга. Рассмотрим несколько реальных сценариев, где эта возможность Python превращает сложный код в простой и понятный.

Обработка данных API



Современные приложения часто работают с данными, получаемыми от различных API в формате JSON. Обработка таких структур — идеальный случай для паттерн-матчинга:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def handle_weather_api_response(response):
    match response:
        case {"current": {"temperature": temp, "humidity": humidity, "wind_speed": wind}, 
              "location": {"city": city, "country": country}}:
            return f"В городе {city} температура {temp}°C, влажность {humidity}%, ветер {wind} м/с"
        
        case {"current": {"temperature": temp}, "location": {"city": city}}:
            return f"В городе {city} сейчас {temp}°C"
        
        case {"error": {"code": code, "message": msg}}:
            return f"Ошибка API {code}: {msg}"
        
        case {"error": msg} if isinstance(msg, str):
            return f"Ошибка: {msg}"
        
        case _:
            return "Неизвестный формат ответа"
 
# Примеры использования
response1 = {
    "current": {"temperature": 25, "humidity": 70, "wind_speed": 5},
    "location": {"city": "Москва", "country": "Россия"}
}
response2 = {"error": {"code": 401, "message": "Unauthorized access"}}
 
print(handle_weather_api_response(response1))
print(handle_weather_api_response(response2))
Без паттерн-матчинга мы бы писали многократные вложенные проверки вида if "current" in response and "temperature" in response["current"]..., что сделало бы код гораздо менее читаемым.

Парсинг командной строки



Обработка аргументов командной строки — ещё одна область, где паттерн-матчинг сияет:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def parse_command(args):
    match args:
        case ["search", *terms] if terms:
            query = " ".join(terms)
            return f"Поиск по запросу: '{query}'"
        
        case ["filter", field, value, *options]:
            options_str = ", ".join(f"{opt}" for opt in options) if options else "по умолчанию"
            return f"Фильтрация по полю '{field}' со значением '{value}', опции: {options_str}"
        
        case ["sort", field, ("asc" | "desc") as direction]:
            return f"Сортировка по '{field}' в {'возрастающем' if direction == 'asc' else 'убывающем'} порядке"
        
        case ["export", ("csv" | "json" | "xml") as format, filename]:
            return f"Экспорт в формате {format.upper()} в файл '{filename}'"
        
        case ["config", key, value]:
            return f"Установка параметра конфигурации '{key}' в значение '{value}'"
        
        case ["help" | "-h" | "--help"]:
            return "Справка по использованию программы"
        
        case [cmd, *_] if cmd not in ["search", "filter", "sort", "export", "config", "help"]:
            return f"Неизвестная команда: {cmd}"
        
        case []:
            return "Не указана команда"
 
# Примеры использования
print(parse_command(["search", "Python", "tutorial"]))
print(parse_command(["sort", "date", "desc"]))
print(parse_command(["export", "json", "data.json"]))
print(parse_command(["unknown", "arg1", "arg2"]))
Этот подход особенно удобен для создания CLI-инструментов, где требуется интерпретировать различные форматы команд.

Обработка вложенных структур данных



Обработка сложных вложенных структур — ещё одна сильная сторона паттерн-матчинга:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
def analyze_shopping_cart(cart):
    match cart:
        case {"items": [], "total": 0}:
            return "Корзина пуста"
        
        case {"items": [{"product": product, "quantity": qty, "price": price}], "total": total} if qty == 1:
            return f"В корзине один товар: {product} за {price}"
        
        case {"items": items, "total": total, "discount": discount} if discount > 0:
            return f"В корзине {len(items)} товаров на сумму {total} со скидкой {discount}%"
        
        case {"items": items, "total": total}:
            count = sum(item.get("quantity", 1) for item in items)
            return f"В корзине {count} товаров на общую сумму {total}"
        
        case {"error": msg}:
            return f"Ошибка при загрузке корзины: {msg}"
 
# Примеры
cart1 = {"items": [{"product": "Ноутбук", "quantity": 1, "price": 50000}], "total": 50000}
cart2 = {
    "items": [
        {"product": "Клавиатура", "quantity": 1, "price": 2000},
        {"product": "Мышь", "quantity": 1, "price": 1000},
        {"product": "Наушники", "quantity": 2, "price": 1500}
    ],
    "total": 6000,
    "discount": 10
}
 
print(analyze_shopping_cart(cart1))
print(analyze_shopping_cart(cart2))
Такой подход позволяет легко анализировать данные электронной коммерции, содержимое баз данных и другие сложные структуры.

Упрощение парсинга JSON



При работе с JSON-данными часто возникает необходимость обрабатывать разные форматы или версии API:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import json
 
def parse_notification(notification_json):
    try:
        data = json.loads(notification_json)
        
        match data:
            case {"notification_type": "message", "sender": sender, "content": content, "timestamp": ts}:
                return f"Сообщение от {sender} в {ts}: {content}"
            
            case {"notification_type": "friend_request", "from": sender}:
                return f"Запрос в друзья от {sender}"
            
            case {"notification_type": "system", "message": msg, "level": "warning"}:
                return f"Системное предупреждение: {msg}"
            
            case {"notification_type": "system", "message": msg, "level": "error"}:
                return f"Системная ошибка: {msg}"
            
            case {"notification_type": type_, **details}:
                return f"Уведомление типа {type_} с деталями: {details}"
            
            case _:
                return "Неизвестный формат уведомления"
    
    except json.JSONDecodeError:
        return "Некорректный JSON-формат"
 
# Примеры
notification1 = '{"notification_type": "message", "sender": "Иван", "content": "Привет!", "timestamp": "2023-11-29 15:30:22"}'
notification2 = '{"notification_type": "system", "message": "Сервер будет перезагружен", "level": "warning"}'
notification3 = '{"notification_type": "friend_request", "from": "Мария"}'
 
print(parse_notification(notification1))
print(parse_notification(notification2))
print(parse_notification(notification3))

Функциональные паттерны: сопоставление результатов вызовов функций



Паттерн-матчинг отлично сочетается с функциональным стилем программирования, особенно при обработке результатов функций:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def fetch_data(resource_id):
    # Имитация запроса к API или базе данных
    if resource_id == 404:
        return None
    elif resource_id == 500:
        return {"error": "Internal server error"}
    elif resource_id < 100:
        return {"status": "pending", "id": resource_id}
    else:
        return {"data": {"id": resource_id, "name": f"Resource-{resource_id}"}, "status": "ok"}
 
def process_resource(resource_id):
    result = fetch_data(resource_id)
    
    match result:
        case {"data": {"name": name}, "status": "ok"}:
            return f"Получен ресурс: {name}"
        
        case {"status": "pending", "id": id}:
            return f"Ресурс {id} ещё обрабатывается"
        
        case {"error": error_msg}:
            return f"Ошибка получения ресурса: {error_msg}"
        
        case None:
            return f"Ресурс с ID {resource_id} не найден"
        
        case unexpected:
            return f"Неожиданный результат: {unexpected}"
 
# Примеры
print(process_resource(123))
print(process_resource(50))
print(process_resource(404))
print(process_resource(500))
Этот пример показывает, как паттерн-матчинг упрощает обработку различных типов возвращаемых значений. Особенно эта техника полезна при работе с функциями, которые могут возвращать разные структуры данных в зависимости от результата операции.

Примеры рефакторинга кода



Рассмотрим пример, как паттерн-матчинг может помочь упростить существующий код:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# Исходный код без паттерн-матчинга
def analyze_transaction_old(transaction):
    if not isinstance(transaction, dict):
        return "Неверный формат транзакции"
    
    if "status" not in transaction:
        return "Отсутствует статус транзакции"
    
    status = transaction.get("status")
    
    if status == "completed":
        if "amount" in transaction and "currency" in transaction:
            return f"Транзакция завершена: {transaction['amount']} {transaction['currency']}"
        else:
            return "Транзакция завершена, но детали отсутствуют"
    
    elif status == "pending":
        if "estimated_completion" in transaction:
            return f"Транзакция в обработке, ожидается завершение {transaction['estimated_completion']}"
        else:
            return "Транзакция в обработке, время завершения неизвестно"
    
    elif status == "failed":
        if "error" in transaction:
            error = transaction["error"]
            if isinstance(error, dict) and "code" in error and "message" in error:
                return f"Ошибка транзакции {error['code']}: {error['message']}"
            else:
                return f"Ошибка транзакции: {error}"
        else:
            return "Транзакция не выполнена, причина неизвестна"
    
    else:
        return f"Неизвестный статус транзакции: {status}"
 
# Рефакторинг с использованием паттерн-матчинга
def analyze_transaction_new(transaction):
    match transaction:
        case {"status": "completed", "amount": amount, "currency": currency}:
            return f"Транзакция завершена: {amount} {currency}"
        
        case {"status": "completed"}:
            return "Транзакция завершена, но детали отсутствуют"
        
        case {"status": "pending", "estimated_completion": date}:
            return f"Транзакция в обработке, ожидается завершение {date}"
        
        case {"status": "pending"}:
            return "Транзакция в обработке, время завершения неизвестно"
        
        case {"status": "failed", "error": {"code": code, "message": message}}:
            return f"Ошибка транзакции {code}: {message}"
        
        case {"status": "failed", "error": error}:
            return f"Ошибка транзакции: {error}"
        
        case {"status": "failed"}:
            return "Транзакция не выполнена, причина неизвестна"
        
        case {"status": status}:
            return f"Неизвестный статус транзакции: {status}"
        
        case _:
            return "Неверный формат транзакции"
Сравнивая эти две функции, мы видим, что версия с паттерн-матчингом:
1. Короче и читабельнее.
2. Не имеет глубокой вложенности условий.
3. Структурно отражает формат данных.
4. Меньше подвержена ошибкам.

Это демонстрирует, как паттерн-матчинг может сделать код более поддерживаемым, особенно при работе со сложными структурами данных.

Обработка иерархических структур данных



Паттерн-матчинг прекрасно подходит для работы с деревьями и другими иерархическими структурами:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
def render_html_element(element):
    match element:
        case {"tag": "img", "attrs": {"src": src, "alt": alt, **rest}}:
            attrs_str = " ".join(f'{k}="{v}"' for k, v in rest.items())
            return f'<img src="{src}" alt="{alt}" {attrs_str}>'
        
        case {"tag": "a", "attrs": {"href": href, **attrs}, "children": children}:
            attrs_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
            inner = "".join(render_html_element(child) for child in children)
            return f'<a href="{href}" {attrs_str}>{inner}</a>'
        
        case {"tag": tag, "children": children, "attrs": attrs}:
            attrs_str = " ".join(f'{k}="{v}"' for k, v in attrs.items())
            inner = "".join(render_html_element(child) for child in children)
            return f'<{tag} {attrs_str}>{inner}</{tag}>'
        
        case {"tag": tag, "children": children}:
            inner = "".join(render_html_element(child) for child in children)
            return f'<{tag}>{inner}</{tag}>'
        
        case {"tag": tag, "text": text}:
            return f'<{tag}>{text}</{tag}>'
        
        case str(text):
            return text
 
# Пример использования
document = {
    "tag": "div",
    "attrs": {"class": "container"},
    "children": [
        {"tag": "h1", "text": "Заголовок"},
        {
            "tag": "p",
            "children": [
                "Это параграф с ",
                {"tag": "a", "attrs": {"href": "https://example.com"}, "children": ["ссылкой"]}
            ]
        },
        {"tag": "img", "attrs": {"src": "image.jpg", "alt": "Изображение", "width": "100"}}
    ]
}
 
print(render_html_element(document))
Такой подход позволяет элегантно обрабатывать сложные древовидные структуры, как в этом примере с HTML-разметкой.

Обработка событий в интерфейсах приложений



Паттерн-матчинг идеально подходит для обработки событий пользовательского интерфейса. В GUI или веб-приложениях часто нужно обрабатывать различные типы событий, каждое со своей структурой данных:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def handle_user_interaction(event):
    match event:
        case {"type": "click", "element": "button", "id": id, "position": (x, y)}:
            return f"Нажатие на кнопку с ID {id} в позиции ({x}, {y})"
        
        case {"type": "input", "element": "textfield", "id": id, "value": value}:
            return f"Ввод текста '{value}' в поле с ID {id}"
        
        case {"type": "drag", "element": element, "from": (x1, y1), "to": (x2, y2)}:
            dx, dy = x2 - x1, y2 - y1
            return f"Перетаскивание {element} на {dx}px вправо и {dy}px вниз"
        
        case {"type": "keypress", "key": "Enter"}:
            return "Нажата клавиша Enter"
        
        case {"type": "keypress", "key": key, "modifiers": mods} if "Ctrl" in mods:
            return f"Нажата комбинация Ctrl+{key}"
        
        case {"type": event_type}:
            return f"Необработанное событие типа {event_type}"
 
# Примеры использования
print(handle_user_interaction({"type": "click", "element": "button", "id": "submit-btn", "position": (120, 300)}))
print(handle_user_interaction({"type": "keypress", "key": "S", "modifiers": ["Ctrl", "Shift"]}))

Обработка состояний конечного автомата



Многие приложения используют конечные автоматы для отслеживания состояний. Паттерн-матчинг предоставляет элегантный способ для обработки переходов между состояниями:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def process_state_transition(current_state, event):
    match (current_state, event):
        case ("idle", "start_task"):
            return "running", "Задача запущена"
        
        case ("running", "complete"):
            return "completed", "Задача успешно завершена"
        
        case ("running", "pause"):
            return "paused", "Задача приостановлена"
        
        case ("running", "error"):
            return "failed", "Задача завершилась с ошибкой"
        
        case ("paused", "resume"):
            return "running", "Задача возобновлена"
        
        case ("paused", "cancel") | ("running", "cancel"):
            return "cancelled", "Задача отменена"
        
        case (state, event_name) if event_name not in ["start_task", "complete", "pause", "resume", "error", "cancel"]:
            return state, f"Неизвестное событие: {event_name}"
        
        case (state, _):
            return state, f"Недопустимый переход из состояния {state}"
 
# Пример использования
state = "idle"
events = ["start_task", "pause", "resume", "complete"]
 
for event in events:
    state, message = process_state_transition(state, event)
    print(f"Событие: {event}, Новое состояние: {state}, Сообщение: {message}")
Этот паттерн особенно полезен в игровом программировании, управлении бизнес-процессами и других системах, основанных на состояниях.

Парсинг DSL (Domain-Specific Language)



Паттерн-матчинг может служить основой для создания простых интерпретаторов предметно-ориентированных языков:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
def evaluate_simple_expression(expr):
    match expr:
        case ["add", a, b]:
            return evaluate_simple_expression(a) + evaluate_simple_expression(b)
        
        case ["subtract", a, b]:
            return evaluate_simple_expression(a) - evaluate_simple_expression(b)
        
        case ["multiply", a, b]:
            return evaluate_simple_expression(a) * evaluate_simple_expression(b)
        
        case ["divide", a, b]:
            divisor = evaluate_simple_expression(b)
            if divisor == 0:
                raise ValueError("Деление на ноль")
            return evaluate_simple_expression(a) / divisor
        
        case ["if", condition, then_expr, else_expr]:
            if evaluate_simple_expression(condition):
                return evaluate_simple_expression(then_expr)
            else:
                return evaluate_simple_expression(else_expr)
        
        case ["equals", a, b]:
            return evaluate_simple_expression(a) == evaluate_simple_expression(b)
        
        case ["greater", a, b]:
            return evaluate_simple_expression(a) > evaluate_simple_expression(b)
        
        case int() | float() as num:
            return num
        
        case _:
            raise ValueError(f"Неизвестное выражение: {expr}")
 
# Пример использования
expression = ["if", 
              ["greater", 10, 5], 
              ["add", 1, 2],
              ["multiply", 3, 4]]
 
result = evaluate_simple_expression(expression)
print(f"Результат: {result}")  # Результат: 3
Такой подход упрощает создание интерпретаторов и компиляторов, делая код более прямолинейным.

Обработка конфигураций и настроек



Паттерн-матчинг удобен для анализа и валидации различных конфигураций:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def validate_database_config(config):
    match config:
        case {"type": "sqlite", "file": file_path}:
            return f"SQLite конфигурация. Файл базы данных: {file_path}"
        
        case {"type": "postgresql", "host": host, "port": port, "database": db, "user": user}:
            return f"PostgreSQL конфигурация. Соединение: {user}@{host}:{port}/{db}"
        
        case {"type": "mysql", "host": host, "database": db, "user": user, "password": _}:
            return f"MySQL конфигурация. База данных: {db} на {host}"
        
        case {"type": db_type, **params}:
            missing = set(["host", "database", "user"]) - set(params.keys())
            if missing:
                return f"Неполная конфигурация для {db_type}. Отсутствуют поля: {', '.join(missing)}"
            return f"Конфигурация для {db_type} с параметрами: {params}"
        
        case _:
            return "Некорректная конфигурация базы данных"
 
# Примеры
print(validate_database_config({"type": "sqlite", "file": "/data/app.db"}))
print(validate_database_config({"type": "postgresql", "host": "localhost", "port": 5432, "database": "app_db", "user": "admin"}))
print(validate_database_config({"type": "mysql", "host": "db.example.com", "user": "app_user"}))
Этот подход особенно полезен при работе с различными типами хранилищ данных, сервисов или внешних API, где каждый тип требует своего набора параметров.

Обработка сетевых протоколов



При разработке сетевых приложений паттерн-матчинг позволяет элегантно обрабатывать различные типы сообщений протокола:

Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def process_network_message(message):
    match message:
        case {"type": "connect", "client_id": client_id, "protocol_version": version} if version >= 2.0:
            return f"Клиент {client_id} подключился по протоколу версии {version}"
        
        case {"type": "connect", "client_id": _, "protocol_version": version}:
            return f"Отказ в подключении: устаревшая версия протокола {version}"
        
        case {"type": "data", "message_id": msg_id, "payload": payload, "checksum": checksum}:
            calculated = calculate_checksum(payload)  # предполагаемая функция
            if calculated == checksum:
                return f"Получено сообщение #{msg_id} с данными длиной {len(payload)} байт"
            return f"Ошибка контрольной суммы в сообщении #{msg_id}"
        
        case {"type": "heartbeat", "timestamp": ts}:
            return f"Получен heartbeat в {ts}"
        
        case {"type": msg_type}:
            return f"Получено неизвестное сообщение типа {msg_type}"
        
        case _:
            return "Некорректный формат сообщения"
Такой подход делает обработку сетевых сообщений более понятной и менее подверженной ошибкам, особенно при наличии сложных протоколов с множеством типов сообщений.

Ограничения и альтернативы



При всей мощи и элегантности паттерн-матчинга в Python у него существуют некоторые ограничения, о которых стоит знать. Понимание этих ограничений поможет избежать неожиданностей при использовании этой функциональности и принять обоснованные решения о том, когда её применять, а когда выбрать другие подходы.

Одним из основных ограничений является невозможность использовать переменные напрямую в качестве значений в шаблонах. Если вам нужно сравнить значение с переменной из внешней области видимости, придётся использовать защитные выражения (guards):

Python
1
2
3
4
5
6
7
8
9
10
11
value = 42
 
# Не сработает как ожидается:
match data:
    case value:  # Здесь 'value' — новая переменная, а не ссылка на внешнюю
        print("Совпадение с 42")
 
# Правильный способ:
match data:
    case x if x == value:  # Использование guard condition
        print("Совпадение с 42")
Ещё одним ограничением при работе со словарями является отсутствие возможности использовать оператор звёздочки (*) для захвата неуказанных ключей, как это можно делать со списками. В качестве альтернативы можно использовать оператор распаковки словаря (**), но это не совсем то же самое с точки зрения синтаксиса.

Для словарей также существует ограничение на использование вычисляемых ключей. Ключи в шаблонах должны быть литералами (строками, числами и т.д.), но не могут быть выражениями или переменными:

Python
1
2
3
4
5
6
key_name = "username"
 
# Не сработает:
match data:
    case {key_name: value}:  # Ошибка: ключи должны быть литералами
        print(f"Имя пользователя: {value}")
Кроме этого, типы шаблонов должны соответствовать типам данных. Например, шаблон для списка не сработает с кортежем и наоборот, даже если структура идентична:

Python
1
2
3
4
5
6
7
data = (1, 2, 3)
 
match data:
    case [1, 2, 3]:  # Не сработает, т.к. data — кортеж, а не список
        print("Список [1, 2, 3]")
    case (1, 2, 3):  # Сработает
        print("Кортеж (1, 2, 3)")
Еще одно ограничение связано с производительностью. При очень сложных шаблонах с множеством вложенных структур и защитных выражений производительность может быть ниже, чем у оптимизированного кода с традиционными условиями. В большинстве случаев это не критично, но для производительно-критичных участков кода стоит провести бенчмаркинг.

Работа с пользовательскими классами требует понимания, что при сопоставлении с экземплярами классов без каких-либо дополнительных действий будут проверяться все атрибуты объекта, а не только те, что указаны в шаблоне. Это может привести к неожиданным результатам.

Типичной ошибкой при использовании паттерн-матчинга является неправильный порядок шаблонов. Важно помнить, что шаблоны проверяются последовательно сверху вниз, и первый подходящий шаблон прерывает дальнейшую проверку. Размещение более общих шаблонов перед более конкретными может привести к тому, что конкретные шаблоны никогда не будут достигнуты:

Python
1
2
3
4
5
6
# Неправильно:
match value:
    case [int(), int()]:  # Этот шаблон перехватит все списки из двух целых чисел
        print("Список из двух целых чисел")
    case [1, 2]:  # Этот шаблон никогда не сработает, т.к. предыдущий уже перехватил [1, 2]
        print("Конкретный список [1, 2]")
Альтернативой паттерн-матчингу в различных ситуациях могут служить:

1. Традиционные условные конструкции (if-elif-else) — подходят для простых случаев или когда логика сложнее, чем просто сопоставление структуры.

2. Словари функций или методов — когда нужно выполнить различные действия в зависимости от значения:

Python
1
2
3
4
5
6
7
8
9
def handle_add(a, b): return a + b
def handle_subtract(a, b): return a - b
 
operations = {
    "add": handle_add,
    "subtract": handle_subtract,
}
 
result = operations.get(operation, lambda a, b: None)(x, y)
3. Полиморфизм и наследование — когда вы работаете с объектно-ориентированным кодом и имеете иерархию классов.

4. Функции высшего порядка и функциональные комбинаторы — для обработки данных в функциональном стиле.

5. Библиотеки для обработки данных, такие как Pandas — для анализа табличных данных.

Выбор между паттерн-матчингом и альтернативами зависит от конкретной задачи, стиля программирования команды и требований к производительности и поддерживаемости кода. Часто паттерн-матчинг упрощает код и делает его более читаемым, но не является серебряной пулей для всех сценариев. При переходе на Python 3.10 и использовании паттерн-матчинга важно учитывать совместимость с предыдущими версиями языка. Если ваш код должен работать в более ранних версиях Python, вам придётся использовать альтернативные подходы или добавить условную логику, проверяющую версию Python.

Словари и списки
Немного не понимаю один момент. Есть словарь, в словаре к значению присвоен список. Знаю, что к элементу списка в словаре можно обратиться как dict....

ЗАДАНИЯ НА СЛОВАРИ И СПИСКИ
Составьте список товаров, имеющихся в магазине, который можно редактировать с учетом завозов. Покупатель делает онлайн заказ, набирая нужные ему...

Словари и списки - социальная сеть
Неделю как занимаюсь с Питоном. Задание: Используя списки и словари установить взаимосвязи пользователей некой соц.сети. С помощью функции...

Списки,Котежи,Словари, Множества!
Проблема в том ,то что всё знать невозможно..вроде разобрался со списками и котрежами,начал вникать в словари и множества,забыл списки и кортежи,что...

Лабораторная работа 3 Списки. Словари
Всем привет,помогите пожалуйста решить задачку на питоне В13. «Уровень жизни» Имеются данные о цене на хлеб в течение 12 месяцев в шести...

Нужно найти ошибку в программе (списки, словари)
Здравствуйте, программа должна считать число слов, которые вводит пользователь( a aa abC aa ac abc bcd a) и выводить их в виде: ac 1 a 2 abc 2 ...

Где на практике в простом приложении можно использовать словари и списки?
Кодер с меня мягко говоря плохой, но читаю питон, стараюсь вникнуть. Практикую прочитанное на простой текстовой RPG, с боевой системой в стиле удар...

Словари в Python
Здравствуйте. Помогите, пожалуйста, с задачей: 1. Надо реализовать фун-ию make_user, которая должна принимать два параметра - имя и...

Словари в Python
Помогите пожалуйста! Имена и адреса электронной почты. Напишите программу, которая сохраняет имена и адреса электронной почты в словаре в виде пар...

Словари Python
В список data вложено n словарей с двумя парами ключ-значение. Пример, где количество словарей n = 3: Из этих этих словарей при помощи циклов...

Python. Словари
dict = {&quot;name&quot;: , &quot;time&quot;: } Как можно увеличить ключ name и time не вручную, а программно ? И возможно ли это вообще ? Допустим, чтобы...

Сопоставление с образцом
Столкнулась с проблемой: перебила программу из книги &quot;Мир Лиспа&quot; Э. Хювёнен, И. Сеппянен, а компилятор постоянно выдает одну и ту же ошибку &quot; A...

Метки pattern matching, python
Размещено в Без категории
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 0
Комментарии
 
Новые блоги и статьи
Обнаружение объектов в реальном времени на Python с YOLO и OpenCV
AI_Generated 29.04.2025
Компьютерное зрение — одна из самых динамично развивающихся областей искусственного интеллекта. В нашем мире, где визуальная информация стала доминирующим способом коммуникации, способность машин. . .
Эффективные парсеры и токенизаторы строк на C#
UnmanagedCoder 29.04.2025
Обработка текстовых данных — частая задача в программировании, с которой сталкивается почти каждый разработчик. Парсеры и токенизаторы составляют основу множества современных приложений: от. . .
C++ в XXI веке - Эволюция языка и взгляд Бьярне Страуструпа
bytestream 29.04.2025
C++ существует уже более 45 лет с момента его первоначальной концепции. Как и было задумано, он эволюционировал, отвечая на новые вызовы, но многие разработчики продолжают использовать C++ так, будто. . .
Слабые указатели в Go: управление памятью и предотвращение утечек ресурсов
golander 29.04.2025
Управление памятью — один из краеугольных камней разработки высоконагруженных приложений. Го (Go) занимает уникальную нишу в этом вопросе, предоставляя разработчикам автоматическое управление памятью. . .
Разработка кастомных расширений для компилятора C++
NullReferenced 29.04.2025
Создание кастомных расширений для компиляторов C++ — инструмент оптимизации кода, внедрения новых языковых функций и автоматизации задач. Многие разработчики недооценивают гибкость современных. . .
Гайд по обработке исключений в C#
stackOverflow 29.04.2025
Разработка надёжного программного обеспечения невозможна без грамотной обработки исключительных ситуаций. Любая программа, независимо от её размера и сложности, может столкнуться с непредвиденными. . .
Создаем RESTful API с Laravel
Jason-Webb 28.04.2025
REST (Representational State Transfer) — это архитектурный стиль, который определяет набор принципов для создания веб-сервисов. Этот подход к построению API стал стандартом де-факто в современной. . .
Дженерики в C# - продвинутые техники
stackOverflow 28.04.2025
История дженериков началась с простой идеи — создать механизм для разработки типобезопасного кода без потери производительности. До их появления программисты использовали неуклюжие преобразования. . .
Тестирование в Python: PyTest, Mock и лучшие практики TDD
py-thonny 28.04.2025
Тестирование кода играет весомую роль в жизненном цикле разработки программного обеспечения. Для разработчиков Python существует богатый выбор инструментов, позволяющих создавать надёжные и. . .
Работа с PDF в Java с iText
Javaican 28.04.2025
Среди всех форматов PDF (Portable Document Format) заслуженно занимает особое место. Этот формат, созданный компанией Adobe, превратился в универсальный стандарт для обмена документами, не зависящий. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru