Як ви правильно здогадалися, у нього є дві сторони: Ловіть будь-яку помилку, вказавши не виключений тип після except
, і просто передайте її, не вживаючи жодних дій.
Моє пояснення "трохи" довше - тому tl; dr воно розпадається на це:
- Не вдавайте жодної помилки . Завжди вказуйте, з яких винятків ви готові відновитись, і лише виловлюйте їх.
- Намагайтеся уникати проникнення, крім блоків . Якщо явно не бажано, це зазвичай не є гарним знаком.
Але давайте детальніше:
Не вдавайте жодної помилки
Використовуючи try
блок, ви зазвичай робите це, оскільки знаєте, що є ймовірність викидання винятку. Як такий, ви також вже маєте приблизне уявлення про те, що може зламатися і який виняток можна кинути. У таких випадках ви ловите виняток, оскільки зможете позитивно відновитись після нього. Це означає, що ви підготовлені до винятку та маєте альтернативний план, який ви будете дотримуватися у випадку цього винятку.
Наприклад, коли ви попросите користувача ввести число, ви можете перетворити вхід, використовуючи int()
який може підняти a ValueError
. Ви можете легко відновити це, просто попросивши користувача спробувати його ще раз, тому ловити ValueError
та спонукати користувача знову було б відповідним планом. Інший приклад може бути, якщо ви хочете прочитати певну конфігурацію з файлу, і цей файл, як буває, не існує. Оскільки це файл конфігурації, у вас може бути деяка конфігурація за замовчуванням як резервна, тому файл точно не потрібен. Таким чином, FileNotFoundError
непоганий план був би вловлювати і просто застосувати конфігурацію за замовчуванням. Зараз в обох цих випадках у нас є дуже специфічний виняток, якого ми очікуємо, і маємо настільки ж конкретний план відновлення після нього. Таким чином, у кожному випадку ми явно лише except
певне виняток.
Однак, якби ми все спіймали , то, окрім тих винятків, з яких ми готові відновитись, є ймовірність, що ми отримаємо винятки, яких ми не очікували і від яких ми справді не можемо відновитись; або не слід одужувати після.
Візьмемо приклад файлу конфігурації зверху. У разі відсутнього файлу ми просто застосували нашу конфігурацію за замовчуванням і, можливо, пізніше вирішимо автоматично зберегти конфігурацію (тому наступного разу файл існує). А тепер уявіть, що ми отримаємо а IsADirectoryError
, або аPermissionError
замість цього. У таких випадках ми, мабуть, не хочемо продовжувати; ми все ще можемо застосувати конфігурацію за замовчуванням, але пізніше ми не зможемо зберегти файл. І, ймовірно, користувач мав намір також мати власну конфігурацію, тому використання значень за замовчуванням, ймовірно, не бажане. Тож ми хотіли б негайно розповісти про це користувачеві, і, ймовірно, також перервати виконання програми. Але це не те, що ми хочемо зробити десь в глибині якоїсь невеликої кодової частини; це щось важливе на рівні додатків, тому з ним слід звертатися вгорі, тому нехай виняток спливає.
Ще один простий приклад також згадується в документі ідіоми Python 2 . Тут у коді існує проста помилка, яка змушує його зламатись. Оскільки ми ловимо всі винятки, ми також ловимо NameError
s та SyntaxError
s . Обидва - це помилки, які трапляються з усіма нами під час програмування; і те і інше - це помилки, які ми абсолютно не хочемо включати при доставці коду. Але оскільки ми їх також спіймали, ми навіть не будемо знати, що вони там відбулися, і втратити будь-яку допомогу, щоб налагодити це правильно.
Але є й більш небезпечні винятки, до яких ми навряд чи готові. Наприклад, SystemError - це звичайно те, що трапляється рідко і чого ми не можемо реально планувати; це означає, що відбувається щось складніше, щось, що, ймовірно, заважає нам продовжувати поточне завдання.
У будь-якому випадку, малоймовірно, що ви готові до всього в невеликій масштабі частини коду, тож саме там вам слід вловлювати лише ті винятки, до яких ви готові. Деякі люди пропонують принаймні зловити, Exception
оскільки це не буде включати такі речі, як SystemExit
і KeyboardInterrupt
які за задумом мають припинити вашу заявку, але я б стверджував, що це все ще занадто неспецифічно. Є лише одне місце, де я особисто приймаю лову Exception
або просто будь-якевиняток, і це в єдиному глобальному обробнику винятків на рівні додатків, який має єдину мету реєструвати будь-які винятки, до яких ми не були готові. Таким чином, ми все ще можемо зберігати стільки інформації про несподівані винятки, яку потім можемо використовувати для розширення нашого коду для явного поводження з ними (якщо ми можемо їх відновити) або - у випадку помилки - для створення тестових випадків, щоб переконатися це не повториться. Але звичайно, це працює лише в тому випадку, якщо ми лише коли-небудь спіймали ті винятки, яких ми вже очікували, тому ті, яких ми не очікували, природно заграють.
Намагайтеся уникати проникнення, крім блоків
Коли явно вибираємо невеликий вибір конкретних винятків, є багато ситуацій, в яких ми будемо добре, просто нічого не зробивши. У таких випадках просто мати except SomeSpecificException: pass
просто чудово. Більшу частину часу, однак, це не так, оскільки, ймовірно, нам потрібен код, пов'язаний з процесом відновлення (як згадувалося вище). Це може бути, наприклад, щось, що повторює дію знову, або встановити значення за замовчуванням.
Якщо це не так, наприклад, тому що наш код вже структурований для повторення, поки він не вдається, то просто проходження досить добре. Беручи наш приклад зверху, ми можемо попросити користувача ввести число. Оскільки ми знаємо, що користувачі люблять не робити те, про що ми їх просимо, ми можемо просто поставити це в цикл в першу чергу, щоб це могло виглядати так:
def askForNumber ():
while True:
try:
return int(input('Please enter a number: '))
except ValueError:
pass
Оскільки ми продовжуємо намагатися, поки не буде винято жодного винятку, нам не потрібно робити нічого особливого, крім блоку, тому це добре. Але звичайно, можна стверджувати, що ми хоча б хочемо показати користувачеві якесь повідомлення про помилку, щоб сказати йому, чому він повинен повторити введення.
У багатьох інших випадках, однак, просто передача в знак except
- це ознака того, що ми насправді не були готові до виключення, яке ми ловимо. Якщо ці винятки не є простими (як-небудь ValueError
чи TypeError
), і причина, чому ми можемо пройти, очевидна, намагайтеся уникати просто проїзду. Якщо насправді нічого не робити (і ви абсолютно впевнені в цьому), тоді подумайте про додавання коментаря, чому так; в іншому випадку розгорніть блок виключення, щоб фактично включити якийсь код відновлення.
except: pass
Хоча найгіршим злочинцем є поєднання обох. Це означає, що ми охоче вловлюємо будь-яку помилку, хоча ми абсолютно не готові до неї, і також нічого не робимо з цим. Ви, принаймні, хочете записати помилку, а також, ймовірно, повторно скористатись нею, щоб все-таки припинити роботу програми (навряд чи ви зможете продовжувати працювати як звичайно після MemoryError). Хоча тільки проходження не тільки збереже додаток дещо живим (залежно від того, де ви зловили звичайно), але й викине всю інформацію, унеможлививши виявлення помилки - що особливо вірно, якщо ви не той, хто її відкрив.
Отже, суть полягає в тому, що: Ловіть лише ті винятки, яких ви справді очікуєте, і готові до відновлення; всі інші, ймовірно, або помилки, які ви повинні виправити, або щось, до чого ви не готові. Пройти конкретні винятки чудово, якщо вам дійсно не потрібно щось робити з ними. У всіх інших випадках це лише ознака презумпції та лінивості. І ви точно хочете це виправити.
logging
модуль на рівні DEBUG, щоб уникнути їх виведення у виробництво, але тримати їх доступними в розвитку.