Використання спробуйте vs якщо в python


145

Чи є обґрунтуванням вирішити, яку з конструкцій використовувати tryабо ifвикористовувати при тестуванні змінної, щоб мати значення?

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

result = function();
if (result):
    for r in result:
        #process items

або

result = function();
try:
    for r in result:
        #process items
except TypeError:
    pass;

Пов’язана дискусія:

Перевірка наявності члена в Python


Майте на увазі, що якщо ваша строфа #process items може підкинути TypeError, вам потрібно перетворити його в іншу спробу: крім: block. Для цього конкретного прикладу я просто використовую if:
richo

Відповіді:


237

Ви часто чуєте, що Python заохочує стиль EAFP ("простіше просити пробачення, ніж дозволу") над стилем LBYL ("дивись, перш ніж стрибнути"). Для мене це питання ефективності та читабельності.

У вашому прикладі (скажіть, що замість повернення списку чи порожнього рядка функцією було повернення списку або None), якщо ви очікуєте, що 99% часу resultнасправді буде містити щось ітерабельне, я використовував би такий try/exceptпідхід. Це буде швидше, якщо справді винятки будуть винятковими. Якщо resultце Noneбільш ніж на 50% часу, то з допомогоюif , напевно , краще.

Щоб підтримати це за допомогою декількох вимірювань:

>>> import timeit
>>> timeit.timeit(setup="a=1;b=1", stmt="a/b") # no error checking
0.06379691968322732
>>> timeit.timeit(setup="a=1;b=1", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.0829463709378615
>>> timeit.timeit(setup="a=1;b=0", stmt="try:\n a/b\nexcept ZeroDivisionError:\n pass")
0.5070195056614466
>>> timeit.timeit(setup="a=1;b=1", stmt="if b!=0:\n a/b")
0.11940114974277094
>>> timeit.timeit(setup="a=1;b=0", stmt="if b!=0:\n a/b")
0.051202772912802175

Отже, хоча ifзаява завжди коштує вам, налаштовувати try/exceptблок майже безкоштовно . Але коли Exceptionнасправді відбувається, вартість значно вища.

Мораль:

  • Це абсолютно добре (і "пітонічний") у використанні try/except для контролю потоку,
  • але це має сенс, коли Exceptions насправді виняткові.

З документів Python:

ЄАФП

Простіше просити пробачення, ніж дозволу. Цей загальний стиль кодування Python передбачає існування дійсних ключів або атрибутів і виловлює винятки, якщо припущення виявиться помилковим. Цей чистий і швидкий стиль характеризується наявністю багатьох tryі exceptвисловлювань. Метод контрастує зі стилем LBYL, поширеним для багатьох інших мов, таких як C.


1
Дякую. Я бачу, що в цьому випадку обґрунтуванням може бути очікування результату.
artdanil

6
.... і це одна (з багатьох) причин, чому так важко зробити справжню оптимізацію JIT для Python. Як показало нещодавно в бета-версії LuaJIT 2, динамічні мови можуть бути дуже, дуже швидкими; але це сильно залежить від початкового дизайну мови та стилю, який він заохочує. (у відповідній примітці, мовний дизайн - це те, чому навіть найкращі JS JavaScirpt не можуть порівнюватись з LuaJIT 1, набагато менше 2)
Хав'єр

1
@ 2rs2ts: Я просто робив подібні таймінги сам. У Python 3 try/exceptбуло на 25% швидше, ніж if key in d:у випадках, коли ключ був у словнику. Це було набагато повільніше, коли ключ не був у словнику, як очікувалося, і відповідав цій відповіді.
Тім Піцкер

5
Я знаю, що це стара відповідь: використання тверджень, таких як 1/1timeit, не є чудовим вибором, оскільки вони будуть оптимізовані (див. dis.dis('1/1')Та зауважте, що поділу не відбувається).
Андреа Корбелліні

1
Це також залежить від операції, яку ви готові провести. Один поділ швидкий, виявити помилку дещо дорого. Але навіть у випадках, коли вартість обробки винятків є прийнятною, уважно подумайте про те, що ви вимагаєте зробити від комп'ютера. Якщо сама операція дуже дорога незалежно від її результатів, і є дешевий спосіб сказати, чи можливий успіх: використовуйте її. Приклад: не копіюйте половину файлу лише для того, щоб зрозуміти, що не вистачає місця.
Бачсау

14

Ваша функція не повинна повертати змішані типи (тобто список або порожній рядок). Він повинен повернути список значень або просто порожній список. Тоді вам не потрібно буде нічого перевіряти, тобто ваш код руйнується на:

for r in function():
    # process items

2
Я повністю згоден з вами з цього приводу; однак функція не моя, і я просто її використовую.
artdanil

2
@artdanil: Таким чином, ви можете зафіксувати цю функцію в тій, що ви пишете, що працює як та, про яку думає Брендон Корфман.
quamrana

24
яке задає питання: чи повинен обгортка використовувати функцію if або спробувати впоратися з випадком, який не можна повторити?
jcdyer

@quamrana - це саме те, що я намагаюся зробити. Отже, як @jcd вказував, Питання полягає в тому, як мені впоратися з ситуацією у функції обгортки.
артданіл

12

Будь ласка, проігноруйте моє рішення, якщо наданий нами код не очевидний на перший погляд і вам доведеться прочитати пояснення після зразка коду.

Чи можу я припустити, що "значення, яке не повертається" означає, що значення повернення є "Немає"? Якщо так, або якщо значення "no value" є помилковим булевим, ви можете зробити наступне, оскільки ваш код по суті трактує "no value" як "не ітерацію":

for r in function() or ():
    # process items

Якщо function() повертаєте щось, що не відповідає дійсності, ви повторюєте порожній кортеж, тобто ви не виконуєте жодних ітерацій. Це по суті LBYL.


4

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


4

Що з наступного було б більш кращим і чому?

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

(Я згоден з Брендоном Корфманом, хоча: повернення None для "no items" замість порожнього списку не порушено. Це неприємна звичка кодерів Java, яких не слід бачити в Python. Або Java.)


4

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

Я мав на увазі (більш поширений) сценарій:

# keep access counts for different files
file_counts={}
...
# got a filename somehow
if filename not in file_counts:
    file_counts[filename]=0
file_counts[filename]+=1

замість еквівалента:

...
try:
    file_counts[filename]+=1
except KeyError:
    file_counts[filename]=1

Це приклад різниці між підходами, про які згадує Тім Піцкер: Перший - LBYL, другий - EAFP
Манагу

Просто швидка примітка, ++яка не працює в python, використовуйте += 1замість цього.
tgray

3

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

result = function();
try:
    it = iter(result)
except TypeError:
    pass
else:
    for r in it:
        #process items

Як бачите, це досить некрасиво. Я не пропоную цього, але це слід згадати для повноти.


По моїх очах, навмисне набег на помилки типу - це завжди поганий стиль кодування. Розробник повинен знати, чого очікувати, і бути готовим обробляти ці значення без винятку. Кожен раз, коли операція робила те, що мала робити (з точки зору програміста), і очікувані результати є списком або None, завжди перевіряйте результат за допомогою is Noneабо is not None. З іншого боку, також поганий стиль збирати винятки для законних результатів. Виняток становлять несподівані речі. Приклад: str.find()повертає -1, якщо нічого не знайдено, тому що сам пошук завершився без помилок.
Бахсау

1

Що стосується продуктивності, використання пробного блоку для коду, який зазвичай не збільшує винятки, швидше, ніж використання оператора if щоразу. Отже, рішення залежить від ймовірності виняткових випадків.


-5

Як правило, ви ніколи не повинні використовувати "try / catch" або будь-який інший елемент обробки даних для контролю потоку. Незважаючи на те, що ітерація за лаштунками контролюється за допомогою StopIterationвинятку винятків, ви все-таки повинні віддати перевагу своєму першому фрагменту коду другому.


7
Це правда в Java. Python досить сильно охоплює EAFP.
fengb

3
Це все ще дивна практика. Мізки більшості програмістів є провідними, щоб пропустити EAFP. Вони зрештою задаються питанням, чому був обраний певний шлях. Це, як кажуть, є час і місце, щоб порушити кожне правило.
ilowe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.