От чего зависит, сможет ли contextlib.suppress подавить ошибку при входе другого контекстного менеджера в составном with?
Способность contextlib.suppress подавить ошибку при входе другого менеджера зависит от порядка менеджеров. Если suppress уже вошёл в контекст, он сможет перехватить исключение при входе последующего менеджера. Если ошибающийся менеджер расположен раньше, suppress ещё не будет активен и исключение не подавит.
Контекстные менеджеры отделяют подготовку ресурса от его гарантированного освобождения. contextlib.suppress появился как компактный способ явно выразить намеренное игнорирование отдельных исключений вместо более громоздкого пустого обработчика try/except.
При этом suppress остаётся обычным контекстным менеджером. Поэтому на него распространяется общий порядок входа и выхода из составного with: менеджеры входят слева направо, а выходят справа налево.
В составном with легко ошибочно считать, что suppress действует на весь оператор независимо от позиции. На практике он начинает перехватывать исключения только после успешного вызова собственного __enter__.
Неверный порядок может привести к тому, что ошибка при открытии ресурса покинет with, хотя разработчик ожидал её подавления. Обратная перестановка может скрыть ошибку и оставить программу без явного сигнала о невозможности открыть ресурс, поэтому подавлять следует только ожидаемые исключения.
При записи нескольких менеджеров Python обрабатывает их как вложенные контексты. Сначала вызывается __enter__ первого менеджера, затем __enter__ второго. Если вход во второй менеджер завершается исключением, вызывается __exit__ уже вошедших менеджеров в обратном порядке.
Поэтому в варианте, где suppress стоит первым, его __enter__ уже выполнен к моменту входа следующего менеджера. Ошибка второго менеджера передаётся в suppress.__exit__, который возвращает истинное значение для подавляемого типа исключения.
Если первым стоит ресурсный менеджер и ошибка возникает в его __enter__, до suppress управление не доходит. Его __enter__ не был вызван, следовательно, его __exit__ также не вызывается.
В первом случае ValueError подавляется: suppress уже активен. Во втором случае исключение возникает до входа в suppress, поэтому оно покидает with.
Есть важное ограничение: подавление исключения не означает успешного создания ресурса. Если ошибка входа была скрыта, код после with должен корректно работать с тем, что ресурс фактически не был получен. Обычно безопаснее сначала явно определить, допустима ли такая ситуация, и подавлять только конкретные ожидаемые типы исключений.
Сервис по очереди открывает необязательные источники данных. Разработчик использовал составной with, поставив файловый ресурс перед suppress, и ожидал, что ошибка доступа будет проигнорирована. Но ошибка возникала в __enter__ файла до активации suppress, поэтому обработка не срабатывала.
Рассматривались два варианта. Можно было обернуть каждый потенциально ошибочный ресурс отдельным with suppress(...): это делает область подавления очевидной, но увеличивает вложенность. Можно было поставить suppress первым в составном with: запись короче, однако порядок становится существенным и может быть менее заметен при добавлении новых менеджеров.
Выбран был отдельный локальный блок suppress вокруг операции открытия. Он точнее показывает намерение, не скрывает ошибки других ресурсов и позволяет после блока явно проверить, был ли источник доступен. В результате подавлялась только ожидаемая ошибка конкретного необязательного источника.
Вопрос: Вызывается ли __exit__ менеджера, если исключение произошло в его __enter__?
Ответ: Нет. __exit__ вызывается только после успешного входа в соответствующий менеджер. Если __enter__ завершился исключением, этот менеджер не считается вошедшим в контекст. Однако __exit__ ранее успешно вошедших менеджеров вызывается, поэтому они могут освободить уже захваченные ресурсы.
Вопрос: Что произойдёт, если suppress подавит ошибку входа последующего менеджера в составном with?
Ответ: Тело with не выполнится, потому что последующий менеджер не смог войти. После подавления управление продолжится с оператора, следующего за всем составным with. Это не превращает неудачный вход в успешное получение ресурса и не гарантирует инициализацию переменной после as.
Вопрос: Почему широкое подавление Exception опаснее подавления конкретного типа?
Ответ: Exception включает множество независимых ошибок, в том числе программные дефекты, ошибки формата данных и нарушения предусловий. Их подавление маскирует причину сбоя и может привести к продолжению работы с некорректным состоянием. Практически безопаснее указывать минимальный ожидаемый тип исключения и ограничивать область suppress одной конкретной операцией.