contextlib.suppress проглотил нужную ошибку: сузить участок очистки
После обработки временного файла сервис удаляет его в finally. Отсутствующий файл при очистке допустим, поэтому вокруг функции появляется suppress(FileNotFoundError). Вскоре пропавший входной файл тоже перестаёт считаться ошибкой, а вызывающий код получает пустой результат.
У исключения есть место возникновения
Один класс исключения может обозначать разные события. FileNotFoundError при чтении обязательного входа означает невозможность выполнить операцию. Тот же тип при удалении временного результата может означать, что другой участник уже сделал нужную работу. Решение о подавлении зависит от операции, поэтому тип исключения без узкой границы блока недостаточен.
Когда suppress охватывает чтение, преобразование и удаление, ошибка теряет контекст. Добавленный позже вызов тоже автоматически попадает под старую политику подавления. Особенно неприятен неявный возврат None: обработчик верхнего уровня может принять его за отсутствие данных и сформировать успешный ответ. Найти исходную причину после этого сложнее, чем сохранить её в месте возникновения.
Начните с требования к функции. Вход обязателен. Временный файл нужен только во время обработки. При любой ошибке его надо попытаться удалить. Отсутствие временного файла во время удаления допустимо. Ошибка доступа к каталогу требует внимания и не входит в разрешённые исходы. Этого достаточно, чтобы выбрать единственное место для suppress.
Вложить обработку в finally
Пример использует отдельный временный каталог, читает исходный текст и пишет промежуточный файл. В finally подавляется только FileNotFoundError непосредственно от unlink. При пропавшем source ошибка выходит наружу. При удалении scratch внутри операции финальная очистка проходит спокойно. Так один тест проверяет обязательность входа, второй — повторяемость завершения.
В production имя промежуточного файла должно принадлежать конкретной операции. Общий scratch.txt в каталоге сервиса создаст гонку между запросами; здесь отдельный TemporaryDirectory изолирует каждый запуск примера. Если временный файл нужен только для накопления байтов, рассмотрите готовые средства tempfile с подходящим сроком жизни вместо самостоятельного придумывания имён.
from contextlib import suppress
from pathlib import Path
from tempfile import TemporaryDirectory
def convert(source, scratch, remove_early=False):
try:
value = source.read_text(encoding="utf-8").upper()
scratch.write_text(value, encoding="utf-8")
result = scratch.read_text(encoding="utf-8")
if remove_early:
scratch.unlink()
return result
finally:
with suppress(FileNotFoundError):
scratch.unlink()
with TemporaryDirectory() as folder:
root = Path(folder)
source, scratch = root / "in.txt", root / "scratch.txt"
source.write_text("данные", encoding="utf-8")
assert convert(source, scratch) == "ДАННЫЕ"
assert not scratch.exists()
assert convert(source, scratch, remove_early=True) == "ДАННЫЕ"
try:
convert(root / "missing.txt", scratch)
except FileNotFoundError:
missing_rejected = True
else:
raise AssertionError("missing input must fail")
print(missing_rejected, scratch.exists())Не проверять существование заранее
Конструкция if path.exists(): path.unlink() оставляет промежуток между проверкой и удалением. Файл может исчезнуть именно в этом промежутке. Попытка выполнить операцию с обработкой конкретного допустимого исхода выражает правило точнее. При этом suppress(Exception) сделал бы невидимыми и проблемы разрешений, и ошибки реализации; перечисляйте только те исключения, которые действительно соответствуют принятому исходу.
Ошибка в finally может заменить исходное исключение как основную причину неудачи, сохранив его в контексте. Если для вашего приложения важнее первичная ошибка преобразования, понадобится явная политика для ошибки очистки: например, отдельная запись с traceback и сохранение исходной причины. Такое решение стоит принимать на уровне операции, где известны требования к сохранности временных данных.
Проверьте три независимых свойства: успешный результат вычисляется, временный файл исчезает, обязательный отсутствующий вход вызывает исключение. Затем добавьте отказ при записи в собственном тестовом окружении. Не используйте настоящие каталоги сервиса для эксперимента с разрешениями. Преимущество маленького блока suppress в том, что дальнейшее развитие преобразования не расширяет список молча проигнорированных ошибок.
При ревью найдите все suppress в функции и прочитайте только тела их блоков. Для каждого вызова сформулируйте, какой именно допустимый исход он может дать. Если внутри одновременно находятся получение входа и уборка результата, разделите их до следующего расширения функции. Для отсутствующего необязательного файла иногда лучше вернуть явное пустое значение в отдельном загрузчике, название которого отражает эту возможность. Тогда основной обработчик не будет путать отсутствие входа с ошибкой подготовки. Полезно также проверить вызывающую сторону: она должна отличать возвращённую пустую коллекцию от неуспешной операции. Сужение подавления часто обнаруживает старые ошибки, которые раньше выдавались за пустой ответ; это повод восстановить понятный контракт между функциями, а не снова расширять обработчик исключений.
Следующий шаг в практике
Пройдите открытый урок о проверке результатов и исключений и оформите эти три сценария отдельными тестами. В курсе «Python для профессионалов» есть менеджеры контекста: продолжите практику конструкциями, которые связывают получение ресурса с его освобождением.
Продолжить на практике
- Python для продвинутых: ООП, типизация и тестирование ↗
Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
- Python для профессионалов ↗
Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.
