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

В статье разобраны распространенные примеры такого кода и объясняется, к каким последствиям они приводят на практике.

1. Перехват всех исключений через Exception

Проблема:

try:
    result = int(value)
except Exception:
    result = 0

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

Со временем становится трудно понять, почему код выдает неправильный результат, так как информация об исходной ошибке теряется и не доходит до логов.

Решение:

try:
    result = int(value)
except ValueError:
    result = 0

Теперь обрабатывается только та ситуация, которая действительно ожидается в этом месте, а именно некорректный ввод данных.

Код перестает скрывать ошибки, не связанные с преобразованием типов. Если в программе появится другой баг, он сразу станет заметен и не будет замаскирован под обычное поведение.

2. Использование магических значений

Проблема:

if status == 3:
    handle_error()

В этом фрагменте невозможно понять, что означает число 3. Сам код не несет смысла, он требует знания контекста, документации или других частей программы. Через некоторое время даже автору кода становится сложно вспомнить, что именно означает это значение.

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

Решение:

STATUS_ERROR = 3

if status == STATUS_ERROR:
    handle_error()

Теперь значение имеет имя, которое объясняет его назначение. Код начинает читаться как текст, а не как набор условий с абстрактными числами. При изменении значения достаточно поменять его в одном месте, не просматривая весь проект.

3. Изменяемые объекты в аргументах по умолчанию

Проблема:

def add_item(item, items=[]):
    items.append(item)
    return items

Аргументы по умолчанию создаются один раз в момент определения функции. Это значит, что список items будет общим для всех вызовов функции. Данные начинают накапливаться между вызовами, что выглядит как ошибка в логике и трудно объясняется при отладке.

Такие проблемы часто проявляются не сразу, а через некоторое время, что делает их особенно неприятными.

Решение:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

Теперь каждый вызов функции либо получает свой список, либо работает с явно переданным. Поведение функции становится предсказуемым, а скрытая связь между вызовами исчезает.

4. Выполнение логики в init

Проблема:

class Service:
    def __init__(self):
        self.connect()
        self.load_data()

Создание объекта начинает выполнять действия с побочными эффектами. Если на этом этапе возникнет ошибка, объект может оказаться в неполном или некорректном состоянии. Также становится сложно управлять порядком выполнения и тестировать такой код.

Кроме того, невозможно просто создать объект без выполнения всей логики сразу.

Решение:

class Service:
    def __init__(self):
        pass

    def start(self):
        self.connect()
        self.load_data()

Инициализация объекта теперь отделена от выполнения логики. Создание объекта становится безопасным, а запуск необходимых действий происходит явно. Такой код проще тестировать, расширять и контролировать.

5. Проверка типов вместо работы с поведением

Проблема:

if isinstance(obj, list):
    process_list(obj)
elif isinstance(obj, tuple):
    process_tuple(obj)

Код начинает жестко зависеть от конкретных типов данных. При добавлении нового типа приходится изменять существующую логику. Это нарушает гибкость и усложняет масштабирование кода.

Подобный подход плохо сочетается с философией Python, где важнее поведение объекта, а не его точный тип.

Решение:

def process(obj):
    for item in obj:
        handle(item)

Теперь код работает с любым объектом, который поддерживает нужное поведение. Логика становится универсальной и легко расширяемой. Новые типы данных можно использовать без изменения уже существующего кода.

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

Аккуратная работа с такими деталями делает Python-код более понятным, устойчивым и удобным для дальнейшего развития.

В моих материалах на Stepik опубликованы бесплатные уроки, курсы и практические задания по Python. В моем Telegram-канале я регулярно публикую дополнительные материалы по Python и backend-разработке. Там вы можете найти разборы тем, практические примеры, объяснения сложных концепций простым языком и продолжения подобных статей.

Комментарии (10)


  1. BadNickname
    04.02.2026 11:36

    STATUS_ERROR = 3
    
    if status == STATUS_ERROR:
        handle_error()

    Голая константа - это просто магическое число с именем.
    Нет автокомплита, нет группировки "какие вообще статусы бывают", вывод статуса в лог покажет "3" а не что-то осмысленное.

    Специально для таких случаев придумали enum.

    class Status(enum.Enum):
        OK = 1
        PENDING = 2
        ERROR = 3
    
    if status == Status.ERROR:
        handle_error()


    1. Vav1r
      04.02.2026 11:36

      А ещё лучше enum.IntEnum, помогает писать более приятный код если эти статусы будут отправляться в / получаться из API например.



  1. leshchev-artem
    04.02.2026 11:36

    Хабы: Python, Функциональное программирование

    А где здесь функциональное программирование?


  1. gtwenty
    04.02.2026 11:36

    На все эти моменты ругается любой вменяемый линтер.


    1. Andrey_Solomatin
      04.02.2026 11:36

      На логику в конструкторе нет. Хотя пример решени сомнительный объек всё равно будет сломан.


      1. gtwenty
        04.02.2026 11:36

        Честно говоря, думал, что условный Flake8 с плагином wemake ругнётся на логику в конструкторе, но, как оказалось, я перепутал это с правилом WPS412, согласно которому запрещена любая логика в __init__.py


  1. mrybs
    04.02.2026 11:36

    Статья, видимо, написана ллмкой ради статьи с рекламой телеграм-канала


    1. Andrey_Popov30 Автор
      04.02.2026 11:36

      Раскрою вам секрет, большинство авторов просто так, без никакого фидбека писать статьи не будут. Habr является своеобразной площадкой с которого переливают часть трафика.


      1. mrybs
        04.02.2026 11:36

        Может быть на других сайтах это и так, но на хабре это категорически не приветствуется.

        К тому же, какое мнение сложится о телеграм-канале и о курсах у ЦА? Посты и курсы сгенерированы ллм, а, соответственно, никакой ценности не имеют