Сопоставление с шаблоном (pattern matching) с помощью операторов match и case было добавлено в Python 3.10 в 2021 году. Паттерн-матчинг позволяет писать более читаемый и лаконичный код, сокращая количество условных операторов и циклов. Он особенно полезен при работе со сложными иерархическими данными, такими как JSON, XML или YAML Операторы match и case имеют следующий синтаксис:
match выражение:
    case шаблон_1:
        ...
    case шаблон_n:
        ... 
    case _:
        ... 
Если выражение совпадает с шаблоном, то выполняется код внутри блока case.
Выражение case _: совпадает с любыми случаями. Если мы поместим его в самое начало то код внутри него будет постоянно выполнятся так как значение сопоставляется последовательно с каждым шаблоном, а это значит что выполняется блок, который совпал первым.
Вот простой пример использования:
def a_or_b_or_c(value):
    match value:
        case "._":
            print("A")
        case "_...":
            print("B")
        case "_._.":
            print("C")
        case _:
            print("Что-то другое")  
a_or_b_or_c("._") # A
a_or_b_or_c(".._") # Что-то другое
Если я помещу case _: в начало, то в обоих случаях будет выведено "Что-то другое".
match/case не просто замена if/else. Его истинное предназначение скрыто в самом названии - сопоставление с шаблонами.
Например мы можем сопоставлять коллекции:
def handle_message(message):
    match message:
        case [name, _, _, final]: # любая последовательность из 4-х элементов
            print(name, final)
        case ["Aboba", _, _, *rest]: # любая последовательность, которая начинается с "Aboba"
            print("1", rest)
        case [str(name), _, _, (float(lat), float(lon))]: # типизированная последовательность, состоящая из пяти элементов
            print(name, lat, lon)
        case [name, *rest, something]: # любая последовательность из n элементов
            print(rest)
        case _:
            print("nothing")
handle_message(["aaaaa", "bbbbb", "ccccc", "ddddd"]) # первый шаблон
handle_message(["1", "bbbbb", "ccccc", "ddddd"]) # второй шаблон
handle_message(["john", "bbbbb", "ccccc", (61.59282, -49.4211)]) # третий шаблон
handle_message(["aaaaa", "bbbbb", "ccccc", "ddddd", "eeeee"]) # четвертый шаблон
handle_message(1234) # пятый шаблон
В этом коде определена функция handle_message, которая обрабатывает входные сообщения в виде списков. Она использует конструкцию match для сравнения входного списка с различными шаблонами и выполняет соответствующие действия.
case [name, _, _, final]:
case ["Aboba", _, _, *rest]:
Aboba. В этом случае функция печатает строку 1 и все элементы списка, кроме первого и последних четырех.case [str(name), _, _, (float(lat), float(lon))]:
case [name, *rest, something]:
case _:
nothing.Если запустить этот код, он будет работать, как ожидалось:
aaaaa ddddd
1 ddddd
john (61.59282, -49.4211)
['bbbbb', 'ccccc', 'ddddd']
nothing
Также match/case может быть вложенным:
from typing import Union
def process_data(data: Union[int, list]) -> str:
    match data:
        case 0:
            return "Ноль"
        case x if isinstance(x, int):
            return f"Целое число: {x}"
        case [a, b]:
            match a:
                case "яблоко":
                    return f"Фрукт: {a}, кол-во: {b}"
                case "банан":
                    return f"Фрукт: {a}, кол-во: {b}"
                case _:
                    return "Неизвестные данные"
        case _:
            return "Неизвестные данные"
print(process_data(0)) # Ноль
print(process_data(42)) # Целое число: 42  
print(process_data(["яблоко", 5])) # Фрукт: яблоко, кол-во: 5
print(process_data(["банан", 3])) # Фрукт: банан, кол-во: 3
print(process_data(["a", 10])) # Неизвестные данные
print(process_data("random")) # Неизвестные данные
Функция process_data:
data типа Union[int, list], что означает, что функция может принимать целое число или список.match, которая позволяет сравнивать data с различными значениями и возвращать соответствующие строки.Варианты обработки data:
data равно 0, функция возвращает строку Ноль.data является целым числом, функция возвращает строку в формате Целое число: <число>.Если data является списком, функция использует внутреннюю конструкцию match для сравнения первого элемента списка (a) с конкретными значениями.
a равно "яблоко", функция возвращает строку в формате Фрукт: {fruit}, кол-во: {count}.a равно "банан", функция также возвращает строку в формате Фрукт: {fruit}, кол-во: {count}.Если data не является ни целым числом, ни списком, функция также возвращает строку "Неизвестные данные".
Как видите, в case даже используется оператор if.
match/case и if/elseЯ прочитал статью на Хабре о том, что match/case работает медленнее чем if/else. Эксперимент в статье был проведен в версии Python 3.10. Сейчас же актуальной версией является Python 3.12.3. Мне стало интересно, является ли эта информация актуальной или сейчас дела обстоят иначе.
В той статье приведен такой код:
import random as rnd
import timeit
def create_rnd_data():
    words = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0,
        'здесь', 'дом', 'да', 'потому', 'сторона',
        'какой-то', 'думать', 'сделать', 'страна',
        'жить', 'чем', 'об', 'последний', 'случай',        
        'голова', 'более', 'делать', 'что-то', 'смотреть',
        'ребенок', 'просто', 'конечно', 'сила', 'российский',
        'конец', 'перед', 'несколько']    
    data = rnd.choices(words, k=500000)
    return data
def test_if(data):
    for word in data:
        if word in ['дом', 'думать', 'что-то', 'просто']:
            pass
        elif isinstance(word, int):
            pass
        elif isinstance(word, str) and len(word) > 3:
            pass
        elif isinstance(word, str) and word.startswith("д"):
            pass
        else:
            pass
# те же проверки при помощи match/case
def test_match(data):
    for word in data:
        match word:
            case 'дом'|'думать'|'что-то'|'просто':
                pass
            case int(word):
                pass
            case str(word) if len(word) > 3:
                pass
            case str(word) if word.startswith("д"):
                pass
            case _:
                pass
# создаем случайные данные для теста
test_data = create_rnd_data()
# количество повторений
repeats = 100
# считаем результаты
time_repeat_if = timeit.timeit("test_if(test_data)", setup="from __main__ import test_if, test_data", number=repeats)
time_repeat_match = timeit.timeit("test_match(test_data)", setup="from __main__ import test_match, test_data", number=repeats)
print("РЕЗУЛЬТАТ IF/ELSE:   ", time_repeat_if/repeats)
print("РЕЗУЛЬТАТ MATCH/CASE:", time_repeat_match/repeats)
Результат в таком тесте был следующий:
РЕЗУЛЬТАТ IF/ELSE:    0.03925879499991424
РЕЗУЛЬТАТ MATCH/CASE: 0.24128189100010786
Разница в этом прогоне составила 6 с небольшим раз! Теперь берем второй тест из статьи:
def create_rnd_data():
    names = ["phone", "TV", "PC", "car", "home", "case", "bird", "chicken", "dish", "float", "C++", "data", ""]
    prices = [500, 100, 1400, 2000, 750, 3500, 5000, 120, 50, 4200]
    goods = []
    for i in range(500000):
        name = names[i%len(names)]
        price = prices[i%len(prices)]
        goods.append({"name": name, "price": price})
    return goods
def test_if(data):
    for element in data:
        if element.get("name") in ["phone", "TV"] and isinstance(element.get("price"), int) and element.get("price") > 2000:
            pass
        elif element.get("name") == "case" and isinstance(element.get("price"), int) and element.get("price") <= 750:
            pass
        elif element.get("name") == "case" and isinstance(element.get("price"), int) and element.get("price") == 750:
            pass
        elif isinstance(element.get("name"), str) and element.get("name"):
            pass
        elif isinstance(element.get("price"), int) and element.get("price") > 1000:
            pass
        else;
            pass
def test_match(data):
    for element in data:
        match element:
            case {"name": "phone"|"TV", "price": int(price)} if price > 2000:
                pass
            case {"name": "case", "price": int(price)} if price <= 750:
                pass
            case {"name": "case", "price": 750}:
                pass
            case {"name": str(name), "price": _} if name:
                pass
            case {"name": _, "price": int(price)} if price > 1000:
                pass
            case _:
                pass
В этот раз результат был следующим:
РЕЗУЛЬТАТ IF/ELSE:    0.06701861700013978
РЕЗУЛЬТАТ MATCH/CASE: 0.5592147159998422
match/case снова оказывается медленнее. НО код с match/case более читабельный и понятный. Это та цена, которую надо платить за удобство, весь Python устроен так.
Небольшой оффтоп, так как в кругах программистов могут неправильно понять. Говоря о Python, важно понимать сферу его применения. На этом языке чаще всего пишут приложения по типу web серверов, ботов, CLI приложений и тд, а это все объединяется одним термином - I/O bound нагрузка. Это означает, что время выполнения больше зависит не от скорости процессора, а от времени ожидания ввода/вывода. В программах такого типа скорость работы ЯП не сильно важна, так как основную часть времени программа ждет данных (работа с сетью, работа с БД, пользовательский ввод), поэтому веб сервер чаще пишут на JS или Python, чем на C/C++ или на чем-то более низкоуровневом. Но все же, отдельные компоненты веб серверов могут быть написаны на более быстрых языках, но только в случае, когда какой-то элемент становится слишком медленным, из-за чего страдает производительность в целом.
Объектно-ориентированное программирование - одна из самых распространенных парадигм. Знание ООП позволит сделать код более гибким и масштабируемым
Для написания сложных программ необходимо использовать сторонние библиотеки. Они позволяют использовать готовый функционал. Также можно использовать модули для компоновки вашего приложения.
В этой статье описано, что означают различные классы исключений. Также рассмотрено создание собственных классов исключений.
Обработка исключений — это важная часть программирования на языке Python, которая позволяет программе продолжать выполнение даже в случае возникновения ошибок. В этой статье мы рассмотрим основные принципы и методы обработки исключений, а также узнаем, почему они так важны для создания надежных и стабильных программ.