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