Блог Михаила | Python | Разработка | Best Practices
"Всем привет! Меня зовут Михаил, я веду Telegram-канал «Python Шпильки», где делюсь изящными приемами программирования. Сегодня хочу показать один из самых полезных паттернов..."
Введение
Представьте: перед вами 200 строк кода, сплошь состоящих из if-elif-else конструкций. Каждый новый condition — еще одна ветка, еще сложнее читать, еще страшнее поддерживать. Знакомо?
В этой статье я покажу изящный прием, который превратит ваши многоэтажные условия в плоский, легко расширяемый и тестируемый код. Всего за 5 минут вы научитесь писать код, который коллеги будут показывать как пример для подражания.
Проблема: когда условия захватывают код
Допустим, мы пишем обработчик заказов в интернет-магазине:
python
def handle_order(status, order_data):
if status == "new":
validate_order(order_data)
process_payment(order_data)
send_confirmation_email(order_data)
elif status == "processing":
check_inventory(order_data)
assign_to_warehouse(order_data)
update_tracking(order_data)
elif status == "shipped":
send_tracking_email(order_data)
update_analytics(order_data)
elif status == "cancelled":
process_refund(order_data)
notify_customer_service(order_data)
update_inventory(order_data)
elif status == "refunded":
close_financial_transactions(order_data)
archive_order(order_data)
# ... и так еще 10-15 статусов
Что здесь не так:
❌ Код растет вертикально с каждым новым статусом
❌ Сложно тестировать отдельные сценарии
❌ При изменении одного статуса нужно перелопачивать всю функцию
❌ Новый разработчик будет разбираться часами
Решение: словарь диспетчеризации функций
А теперь посмотрите на эту же логику, переписанную с помощью паттерна "Словарь диспетчеризации":
python
class OrderProcessor:
def __init__(self):
self.handlers = {
"new": self._handle_new,
"processing": self._handle_processing,
"shipped": self._handle_shipped,
"cancelled": self._handle_cancelled,
"refunded": self._handle_refunded,
}
def process_order(self, status, order_data):
handler = self.handlers.get(status, self._handle_unknown)
return handler(order_data)
def _handle_new(self, order_data):
validate_order(order_data)
process_payment(order_data)
return send_confirmation_email(order_data)
def _handle_processing(self, order_data):
check_inventory(order_data)
assign_to_warehouse(order_data)
return update_tracking(order_data)
def _handle_shipped(self, order_data):
send_tracking_email(order_data)
return update_analytics(order_data)
def _handle_cancelled(self, order_data):
process_refund(order_data)
notify_customer_service(order_data)
return update_inventory(order_data)
def _handle_refunded(self, order_data):
close_financial_transactions(order_data)
return archive_order(order_data)
def _handle_unknown(self, order_data):
logger.warning(f"Unknown order status: {order_data.get('status')}")
return {"error": "Unknown status"}
# Использование:
processor = OrderProcessor()
result = processor.process_order("new", order_data)
Почему это решение элегантнее?
? Принцип единственной ответственности
Каждый метод отвечает только за один статус. Изменили логику отмены? Правите только handlecancelled.
? Простота тестирования
python
def test_cancelled_order():
processor = OrderProcessor()
test_data = {"id": 123, "amount": 100}
# Тестируем только обработку отмены
result = processor._handle_cancelled(test_data)
assert result["refund_processed"] == True
mock_notify.assert_called_once()
? Легкое расширение
Добавляем новый статус без изменения существующей логики:
python
def add_preorder_status(self):
self.handlers["preorder"] = self._handle_preorder
def _handle_preorder(self, order_data):
reserve_inventory(order_data)
return send_preorder_confirmation(order_data)
? Визуальная чистота
Теперь архитектура вашего обработчика видна как на ладони:
python
# ВСЯ ЛОГИКА ПРЕДСТАВЛЕНА В ОДНОЙ СТРОКЕ
handlers = {
"new": self._handle_new,
"processing": self._handle_processing,
"shipped": self._handle_shipped,
# ...
}
Продвинутые возможности
Динамическая регистрация обработчиков
python
def register_handler(self, status, handler):
self.handlers[status] = handler
# Где-то в другом модуле:
processor.register_handler("preorder", custom_preorder_handler)
Декоратор для автоматической регистрации
python
def register(status):
def decorator(method):
method._handler_status = status
return method
return decorator
class OrderProcessor:
def __init__(self):
self.handlers = {}
self._auto_register_handlers()
def _auto_register_handlers(self):
for attr_name in dir(self):
attr = getattr(self, attr_name)
if hasattr(attr, '_handler_status'):
self.handlers[attr._handler_status] = attr
@register("new")
def handle_new_order(self, order_data):
# ...
Когда использовать этот паттерн
✅ Идеально подходит для:
Обработчиков HTTP-запросов (API роутинг)
State-машин и обработки статусов
Парсеров различных форматов данных
CLI-утилит с множеством команд
❌ Возможно, не стоит использовать:
Когда условий всего 2-3 (оверкиллинг)
Когда логика слишком простая для вынесения в отдельные функции
Когда производительность критична (есть минимальные накладные расходы)
Бенчмарк: если сомневаетесь в производительности
python
import timeit
# if-else подход
def if_else_approach(status):
if status == "new": return 1
elif status == "processing": return 2
# ... 10 условий
else: return 0
# Словарный подход
def dict_approach(status):
return handlers.get(status, lambda: 0)()
handlers = {"new": lambda: 1, "processing": lambda: 2} # ... 10 условий
# Результаты для 100000 вызовов:
# if-else: 0.015 секунд
# dict: 0.012 секунд
Вывод: Словарный подход часто даже быстрее за счет хэш-таблиц!
Заключение
Паттерн "Словарь диспетчеризации" — это не просто синтаксический сахар. Это:
? Архитектурное решение для сложной логики
? Инструмент рефакторинга для борьбы с code smell
? Демонстрация мастерства в Python
Следующие шаги:
Найдите в своем коде самый страшный
if-elif-elseблокПопробуйте переписать его с помощью словаря функций
Поделитесь результатами в комментариях!
А в следующей статье разберем, как комбинировать этот подход с декораторами для создания настоящих произведений искусства в коде.
Теги: Python, Рефакторинг, Паттерны проектирования, Best Practices, Код качество
Понравилась статья? Подписывайтесь на мой Telegram-канал Python Шпильки — там регулярно выходят такие же изящные решения для повседневных задач разработчика.
Комментарии (21)

vadimr
13.10.2025 11:21Очень много синтаксического мусора в виде низачем не нужных имён методов из-за отсутствия в питоне многострочных лямбд.

outlingo
13.10.2025 11:21Когда не знал про паттерн "стратегия", но умеешь пользоваться LLM и генерировать ИИ-шит

AlexanderMelory
13.10.2025 11:21то, что он класс не назвал Strategy, не означает, что он не реализовал данный паттерн лол))
у автора как раз таки и реализован паттерн стратегия, с некоторыми нюансами, но все же, для примера вполне достаточно.
есть хендлеры, есть условия, при которых выбирается нужный хендлер, есть дешевый мапинг. единственное, я бы сделал это в виде отдельных классов и не привязывался к классу OrderProceccor, тем самым создав пространство для масштабирования.
с чем вы тут не согласны?)))

outlingo
13.10.2025 11:21Мне не нравится тем, что это AI-slop и LLM-shit
Но если предположить, что публикатор все же писал статью "вручную"...
Если ты пишешь тег "Паттерны проектирования" - то сошлись, что в этих самых паттернах такой подход называется стратегией.
Вообще, многие разработчики на питоне имеют какую-то антипатию к патернам, аппелируя к тому, что паттерны переусложнены и в них много лишнего - вот смотрите, как можно просто сделать, "тяп-ляп и в продакшн". В результате получается то, что получается - код, насквозь прошитый глобальными переменными, статическими сессиями и глобальными функциями с сайд-эффектами. Да-да, весь этот ад с reserve_inventory(...) - откуда у нее возьмется сессия, в которой она сходит в базу или другой микросервис? Или notify_customer_service(...) - откуда в нем возьмутся реквизиты для доступа к очередям рассылки уведомлений?
Такие простые реализации, которые смело копипастит из ответов LLM автор - без всей этой замученности с типами, классами и прочим - они в рабочей схеме не прокатят. Код хорошо бы прогонять через проверку статической типизации - вот эти вот все from typing import .... и mypy (это вообще категорическое требование - с моей точки зрения, код без них вообще недопустим, кроме случаев безжалостного легаси). А когда привозится статическая типизация, вот тогда и начинается самое интересное - то есть то, чего начинающие разработчики не любят.
Ну а то, что показано в статье - поытка разделить god method - но в результате просто превращает его в god object. Но LLM на это пофиг, чушь на входе - чушь на выходе, обучили на спагетти из статиков, глобальных переменных и сайдэффектов - получите то же самое в ответ на свой запрос. И следующую итерацию обучат на этом же генеративе. Идеальная самоодебиливающаяся система, которая учится быть еще тупее на своем же выхлопе.

AlexanderMelory
13.10.2025 11:21согласен с тем, что большинство разработчиков на питоне забивают на паттерны. просто сам язык позволяет не углубляться в эти знания и шлепать как попало

Petr_axeman
13.10.2025 11:21Вообще, многие разработчики на питоне имеют какую-то антипатию к патернам, аппелируя к тому, что паттерны переусложнены и в них много лишнего - вот смотрите, как можно просто сделать, "тяп-ляп и в продакшн".
Просто в нашей любимой замке паттерны буквально переусложненны. Недавно неплохая статья была на хабре, что-то вроде "Почему паттерны проектирования в питоне не работают". Очень интересная статья которая очень точно описывает часть нюансов отношения змеи и паттернов.
Общий посыл статьи примерно такой - "Зачем шаблоны, если можно и лучше без шаблонов". То что указанное в статье является каким то шаблоном вообще удивление. В некоторых случаях в питоне можно callable метать и по спискам, и интегрироваться по ним. И использовать как замыкания.

outlingo
13.10.2025 11:21Шаблон это подход, принцип реализации задачи. Детали могут отличаться, но суть одна и та же - создается посредник, в котором регистрируются исполнители (стратегии). Принятие решения о том какому исполнителю выполнить действие (какую стратегию использовать) принимает посредник. Вместо прямого вызова исполнителя вызывается посредник.
И далее начинаются уже детали реализации, например будут ли посредники Callable (питон), делегатами (C#) или объектами реализующими интерфейс (Java), будет ли выбор реального актора базироваться на словаре, или стратегии будут предоставлять предикаты которые определяют готовность стратегии обработать вызов, или какой-нибудь другой способ. Много вариантов.
То, что автор не любит слово "шаблон" не значит, что его тут нет :-)

evgenyk
13.10.2025 11:21В случае чуть более-менее сложных логик, описываемый подход (с использованием словаря обработчиков, где значения - обработчики, а ключи - строки воходных логических параметров) все равно значительно лучше, чем как match/case, так и паттерна стратегия, ИМХО. Таким подходом еще деды на ассемблере пользовались. Он прост и надежен, как АК-47.

Lecron
13.10.2025 11:21Если есть обоснованное предположение, что структура функций не будет меняться, либо более сложная логика появится только в редких статус-обработчиках, можно еще больше упростить.
class OrderProcessor: def __init__(self): self.handlers = { "new": [validate_order, process_payment, send_confirmation_email], ... } def __call__(self, order_data): status = order_data['status'] if status in self.handlers: for handler in self.handlers[status]: reuslt = handler(order_data) else: logger.warning(f"Unknown order status: {status}") result = {"error": "Unknown status"} return result

encore-show
13.10.2025 11:21а теперь можно просто выкладывать сгенерированный текст ИИ просто в качестве статьи и называть это Best Practices?
Это же просто копипаст ответа курсора)
LeonidPr
13.10.2025 11:21Этот период надо просто пережить, а аккаунты тех, кто постит эту субстанцию, банить нещадно.
Со временем народ поймет, что это бесполезно.

encore-show
13.10.2025 11:21это факт, просто становится грустно наблюдать что AI который должен вроде как давать знания, так как это доступ к такому объему знаний, а по факту я смотрю как только становится больше глупых и перестающих думать.
Но наверное да, просто нужно время

python_pins_mikhail Автор
13.10.2025 11:21Спасибо за комментарии! Тапок у меня теперь … немерено. Я предложил один из вариантов обхода if-elif-else, и попытался описать этот подход как можно понятнее. Согласен что есть и другие подходы, которые вы написали, за это вам отдельное спасибо!!! Пусть каждый выбирает себе по душе.

K1nGsmaN
13.10.2025 11:21очередной AI-slop. зачем вы с этой ерундой на хабр лезете? кого вы здесь хотите обмануть?
даже не удосужился сообщить, что это обычный паттерн «стратегия». видимо уровень автора - выдавать ответы чата за свои.
pda0
Немного примеров match/case в Python 3.10
Существует уже 4 года. Не благодарите.
Спасибо, запомнил. Ни в коем случае не подписываться.
Valberton
А если у меня проект для 2.7 Пайтона, будет ли резонно использовать этот метод, вместо въевшегося if-else?
qss53770
А если у меня не python 2.7, а проект на перфо картах?
pda0
А если на Cobol? Короче говоря, если вы поддерживаете проект на python 2, поддержка которого прекратилась в 2020 и который сейчас не поставляется в дистрибутивах, то вы достаточно опытный разработчик, чтобы вручную возиться со всем этим старьём и вам не нужны такие советы, сами способны додуматься.
А вот показывать такое новичкам как "изящные решения для повседневных задач разработчика", пожалуйста, не надо.