В чем разница между except и except Exception в Python
Что проверяют
Знаете ли вы иерархию исключений Python и понимаете ли, почему голый except: считается плохой практикой. Следом почти всегда спрашивают, что именно не поймает except Exception и когда всё-таки нужен BaseException.
Как отвечать
Разница упирается в корень иерархии:
except:: без указания класса ловит всё, что наследуетBaseException, то есть вообще любое исключение. Это ровно то же, чтоexcept BaseException:;except Exception:: ловит только наследниковException. Сюда входят почти все рабочие ошибки:ValueError,KeyError,OSError,ZeroDivisionError;- что остаётся снаружи:
KeyboardInterrupt(Ctrl+C),SystemExit(его бросаетsys.exit()),GeneratorExitи, начиная с Python 3.8,asyncio.CancelledError. Они намеренно выведены из-подException: это не ошибки, а сигналы остановки.
Отсюда практический вывод. Голый except: глотает Ctrl+C, мешает sys.exit() завершить процесс и ломает отмену задач в asyncio. Поэтому PEP 8 советует его не писать, а линтеры (ruff, flake8) ругаются правилом E722.
Сильный ответ добавляет третий уровень: ловить нужно самое узкое исключение, которое вы умеете обработать, например except (KeyError, ValueError) as e:. Широкий except Exception уместен на границе системы: в обработчике задачи воркера, в цикле чтения сообщений из очереди, в middleware веб-фреймворка. И там ошибку обязательно логируют через logger.exception(...), чтобы сохранить трейсбек. Если нужно перехватить вообще всё ради очистки ресурсов, пишут except BaseException: с raise в конце или просто finally.
Где спотыкаются
Говорят, что except Exception «ловит всё». Это неверно, и вопрос как раз про это: сигналы остановки проходят мимо.
Пишут except Exception: pass. Формально это лучше голого except, но ошибка всё равно исчезает бесследно, и баг потом ищут неделями.
Перехватывают CancelledError голым except или except BaseException внутри корутины и не пробрасывают его дальше через raise. Задача перестаёт отменяться, а таймауты через asyncio.timeout() не срабатывают.