Краще щось "спробувати" і зловити виняток або тестувати, чи можливо спочатку уникнути виключення?


139

Чи слід перевірити ifщось дійсне або просто tryзробити це і зробити виняток?

  • Чи є якась обґрунтована документація, яка говорить про те, що кращим є один із способів?
  • Чи один із способів більш пітонічний ?

Наприклад, чи слід:

if len(my_list) >= 4:
    x = my_list[3]
else:
    x = 'NO_ABC'

Або:

try:
    x = my_list[3]
except IndexError:
    x = 'NO_ABC'

Деякі думки ...
PEP 20 говорить:

Помилки ніколи не повинні проходити мовчки.
Якщо явно мовчати.

Чи слід інтерпретацію tryзамість ifінтерпретувати як помилку, що проходить безшумно? І якщо так, чи ви явно замовчуєте його, використовуючи таким чином, таким чином, роблячи це нормально?


Я не маю на увазі ситуацій, коли ти можеш робити речі лише 1 спосіб; наприклад:

try:
    import foo
except ImportError:
    import baz

Відповіді:


161

Ви повинні віддавати перевагу try/exceptбільш , if/elseякщо що призводить

  • прискорення (наприклад, запобігання додаткових пошуків)
  • чистіший код (менше рядків / легше читати)

Часто вони йдуть рука об руку.


прискорення

У разі спроби знайти елемент у довгому списку:

try:
    x = my_list[index]
except IndexError:
    x = 'NO_ABC'

спроба, за винятком випадків, є найкращим варіантом, коли index, мабуть, у списку, а IndexError зазвичай не підвищується. Таким чином ви уникнете необхідності додаткового пошукуif index < len(my_list) .

Python заохочує використання винятків, які ви обробляєте - це фраза від Dive Into Python . Ваш приклад не тільки обробляє виключення (коректно), а не дозволяючи йому мовчки пройти , а також виключення відбувається тільки в виняткових випадку індекс не найден (звідси слово виняток !).


чистіший код

В офіційній документації Python згадується EAFP : простіше просити пробачення, ніж дозволу, і Роб Найт зазначає, що введення помилок, а не їх уникнення , може призвести до більш чистого, легшого для читання коду. Його приклад говорить так:

Гірше (LBYL "дивись перед тим, як стрибнути") :

#check whether int conversion will raise an error
if not isinstance(s, str) or not s.isdigit():
    return None
elif len(s) > 10:    #too many digits for int conversion
    return None
else:
    return int(s)

Краще (EAFP: простіше просити пробачення, ніж дозволу) :

try:
    return int(s)
except (TypeError, ValueError, OverflowError): #int conversion failed
    return None

if index in mylistтести з індексом - це елемент мого списку, а не можливий індекс. Ви б хотіли if index < len(mylist)замість цього.
ychaouche

1
Це має сенс, але де я можу знайти документацію, яка дає зрозуміти, які можливі винятки можуть бути кинуті для int (). docs.python.org/3/library/functions.html#int Я не можу знайти цю інформацію тут.
BrutalSimplicity

На противагу, ви можете додати цей випадок (від виключення док. ) На свою відповідь, коли ви повинні віддавати перевагу , if/elseніжtry/catch
rappongy

20

У цьому конкретному випадку вам слід повністю використовувати щось інше:

x = myDict.get("ABC", "NO_ABC")

Взагалі, однак: Якщо ви очікуєте, що тест часто виходить з ладу, використовуйте if. Якщо тест дорогий відносно спроби операції та вилучення виключення, якщо він не вдався, використовуйте try. Якщо не застосовується жодна з цих умов, перейдіть до того, що прочитати простіше.


1
+1 для пояснення під зразком коду, на якому розміщено місце.
ktdrv

4
Я думаю, що цілком зрозуміло, що це не те, про що він питав, і тепер він редагував публікацію, щоб зробити це ще більш зрозумілим.
agf

9

Використання tryта exceptбезпосередньо, а не всередині ifварти, слід завжди робити, якщо є можливість перегонів. Наприклад, якщо ви хочете переконатися, що існує каталог, не робіть цього:

import os, sys
if not os.path.isdir('foo'):
  try:
    os.mkdir('foo')
  except OSError, e
    print e
    sys.exit(1)

Якщо інший потік або процес створює каталог між isdirі mkdir, ви вийдете. Натомість зробіть це:

import os, sys, errno
try:
  os.mkdir('foo')
except OSError, e
  if e.errno != errno.EEXIST:
    print e
    sys.exit(1)

Він вийде лише в тому випадку, якщо каталог "foo" не вдасться створити.


7

Якщо тривіально перевірити, чи щось не вдасться, перш ніж ви це зробите, вам, мабуть, слід віддати перевагу. Зрештою, побудова винятків (включаючи пов'язані з ними відстеження) вимагає часу.

Винятки слід використовувати для:

  1. речі, які несподівано, або ...
  2. речі, де вам потрібно перестрибнути більше ніж один рівень логіки (наприклад, там, де breakвас не виходить досить далеко), або ...
  3. речі, де ви точно не знаєте, що буде обробляти виняток достроково, або ...
  4. речі, коли перевірка заздалегідь на помилку дорога (відносно просто спроби операції)

Зауважте, що часто реальна відповідь - "ні" - наприклад, у першому прикладі, що ви насправді повинні зробити, це просто використовувати .get()для надання за замовчуванням:

x = myDict.get('ABC', 'NO_ABC')

крім того, що if 'ABC' in myDict: x = myDict['ABC']; else: x = 'NO_ABC'насправді часто швидше, ніж використання get, на жаль. Не кажучи, що це найважливіший критерій, але це щось, що слід пам’ятати.
agf

@agf: Краще написати чіткий і стислий код. Якщо що - то має бути оптимізовано пізніше, це легко повернутися і переписати його, але c2.com/cgi/wiki?PrematureOptimization
Amber

Я це знаю ; моя точка зору, що if / elseі try / exceptможе мати місце навіть тоді , коли є альтернативи по кожному конкретному випадку , оскільки вони мають різні характеристики.
agf

@agf, чи знаєте ви, чи буде вдосконалений метод get () у майбутніх версіях, який буде (принаймні) таким швидким, як явний пошук? До речі, коли ви шукаєте двічі (як якщо б "ABC" у d: d ['ABC']), спробуйте: d ['ABC'], за винятком KeyError: ... не найшвидший?
Ремі

2
@Remi Повільна частина .get()- це пошук атрибутів та набір функцій на рівні Python; використання ключових слів на вбудованих модулях в основному відповідає правилу С. Я не думаю, що це стане занадто швидким в будь-який час. Щодо ifпорівняння try, метод read dict.get () повертає вказівник, який містить деяку інформацію про продуктивність. Співвідношення звернень до пропусків має значення ( tryможе бути швидшим, якщо ключ майже завжди існує), як і розмір словника.
agf

5

Як згадують інші посади, це залежить від ситуації. Існує декілька небезпек із застосуванням спроб / за винятком замість того, щоб заздалегідь перевірити достовірність своїх даних, особливо при використанні їх у великих проектах.

  • Код у блоці спробу може мати шанс нанести всі види хаосу до того, як виняток буде винятком - якщо попередньо перевірити заздалегідь за допомогою заяви if, ви можете цього уникнути.
  • Якщо код, викликаний у вашому блоці спробу, викликає загальний тип винятків, наприклад TypeError або ValueError, можливо, ви насправді не можете потрапити на той самий виняток, який ви очікували зловити - це може бути щось інше, що підвищує той самий клас винятку до або після навіть потрапляння на рядок, де може бути підвищений ваш виняток.

Наприклад, припустимо, у вас були:

try:
    x = my_list[index_list[3]]
except IndexError:
    x = 'NO_ABC'

IndexError нічого не говорить про те, чи сталося це при спробі отримати елемент index_list або my_list.


4

Чи слід використовувати спробу замість, якщо трактувати як помилку, що проходить безшумно? І якщо так, чи ви явно замовчуєте його, використовуючи таким чином, таким чином, роблячи це нормально?

Використання try- це визнання, що помилка може пройти, що протилежно тому, як вона проходить безшумно. Використання exceptзмушує його зовсім не проходити.

Використання try: except:є кращим у випадках, коли if: else:логіка є складнішою. Простий - краще, ніж складний; складний краще, ніж складний; і простіше просити пробачення, ніж дозволу.

Про те, що "помилки ніколи не проходять безшумно" попереджає, це той випадок, коли код може створити виняток, про який ви знаєте, і коли ваш дизайн допускає можливість, але ви не створили спосіб боротьби з винятком. Явні глушники помилка, на моєму погляді, буде робити що - щось подібне passв exceptблоці, який повинен бути зроблений тільки з розумінням того, що «нічого не робити» на насправді є правильною обробка помилок в конкретній ситуації. (Це один з небагатьох разів, коли мені здається, що коментар у добре написаному коді, мабуть, справді потрібен.)

Однак у вашому конкретному прикладі жодне з них не підходить:

x = myDict.get('ABC', 'NO_ABC')

Причина, на яку всі вказують це - навіть якщо ви усвідомлюєте своє бажання зрозуміти взагалі та нездатність запропонувати кращий приклад - це те, що еквівалентні побічні кроки насправді існують у дуже багатьох випадках, і шукати їх - це перший крок у вирішенні проблеми.


3

Щоразу, коли ви використовуєте try/exceptдля контролю потоку, запитайте себе:

  1. Чи легко зрозуміти, коли tryблок вдається і коли він виходить з ладу?
  2. Чи знаєте ви про всі побічні ефекти всередині tryблоку?
  3. Вам відомі всі випадки, коли tryблок кидає виняток?
  4. Якщо реалізація tryблоку зміниться, чи буде ваш потік управління все ще вести себе так, як очікувалося?

Якщо відповідь на одне або кілька із цих питань - «ні», можливо, просити багато пробачення; швидше за все, від вашого майбутнього Я.


Приклад. Нещодавно я побачив код у більшому проекті, який виглядав приблизно так:

try:
    y = foo(x)
except ProgrammingError:
    y = bar(x)

Розмовляючи з програмістом, виявилося, що запланований потік управління був:

Якщо x - ціле число, зробіть y = foo (x).

Якщо x - список цілих чисел, зробіть y = bar (x).

Це спрацювало, тому що fooзробив запит до бази даних, і запит був би успішним, якби xбуло ціле число і кинув список ProgrammingErrorif, якщо xбув.

Тут використання try/exceptпоганий вибір:

  1. Назва винятку, ProgrammingErrorне видає фактичної проблеми (тобто xне ціле число), що ускладнює розуміння того, що відбувається.
  2. ProgrammingErrorПіднято під час виклику бази даних, яка час відходи. Речі стали б по-справжньому жахливими, якби виявилося, що fooщось записує в базу даних, перш ніж викине виняток, або змінить стан якоїсь іншої системи.
  3. Незрозуміло, чи ProgrammingErrorпіднімається лише тоді, коли xє список цілих чисел. Припустимо, наприклад, що в fooзапиті бази даних є помилка друку . Це також може призвести до ProgrammingError. Наслідком цього є те, що bar(x)тепер також називається, коли xє цілим числом. Це може призвести до критичних винятків або призведе до непередбачуваних результатів.
  4. The try/exceptБлок додає вимога для всіх реалізацій майбутніх foo. Щоразу, коли ми змінюємося foo, ми мусимо зараз замислюватися над тим, як він обробляє списки, і переконайтесь, що він кидає а, ProgrammingErrorа не, скажімо, AttributeErrorпомилку чи взагалі немає.

0

Для загального значення ви можете розглянути читання ідіоми та антиідіоми в Python: Винятки .

У вашому конкретному випадку, як заявили інші, ви повинні використовувати dict.get():

get (клавіша [, типово])

Поверніть значення для ключа, якщо ключ є у словнику, інакше за замовчуванням. Якщо за замовчуванням не вказано, він за замовчуванням не має значення, щоб цей метод ніколи не викликав KeyError.


Я не думаю, що посилання взагалі висвітлює цю ситуацію. Його приклади стосуються поводження з речами, які представляють реальні помилки , а не про те, чи слід використовувати обробку виключень у очікуваних ситуаціях.
agf

Перша посилання застаріла.
Тимофей Бондарев
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.