Представьте, вы приступаете к работе над новым проектом и обнаруживаете в логах следующие сообщения, которые смешаны с успешными запросами к серверу:
Unexpected exception:
Что-то постоянно ломается, но без каких-либо сообщений.
Если сократить проблему до минимального примера из кода приложения, с которым я недавно столкнулся, получится следующая конструкция, которая на первый взгляд не вызывает подозрений:
try:При выполнении этого примера получим следующий результат:
raise KeyError
except Exception as e:
print(f"Unexpected exception: {e}")
Unexpected exception:
Проблема заключается в том, что есть четыре способа вывести сообщение об исключении:
🐟 print(e)
🐟 print(str(e))
🐟 print(e.message)
🐟 print(repr(e))
Первые два варианта не особенно информативны. Например, если мы попытаемся обратиться к несуществующему ключу в словаре (самая распространенная ошибка в Python), то эти варианты выведут только название ключа.
my_dict = {}
try:
b = my_dict["bad"]
except Exception as e:
print(f"Unexpected exception: {e}")
Unexpected exception: 'bad'Это происходит потому, что str(e) и e выводят только сообщение об ошибке, но не ее тип. Чаще всего этого достаточно, но исключения существуют для того, чтобы информировать о ситуациях, которые не были ожидаемы.
Иногда люди используют print(e.message). Однако здесь возникают две проблемы: во-первых, мы все равно получаем только сообщение об ошибке, а не ее тип. Во-вторых, атрибут message не определен для всех исключений. Если мы не проверим наличие этого атрибута перед использованием, мы получим новое исключение:
AttributeError: 'KeyError' object has no attribute 'message'
Однако магический метод repr, который используется для предоставления наиболее точного описания, справляется с задачей. Рассмотрим сравнение:
try:Unexpected exception: KeyError()
raise KeyError
except Exception as e:
print(f"Unexpected exception: {repr(e)}")
В случае с использованием словаря получим:
Unexpected exception: KeyError('bad')
что является более явным, чем все три предыдущих варианта