Использование try vs if в python

Есть ли обоснование, чтобы решить, какой из 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

9 ответов

Решение

Вы часто слышите, что 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 для контроля потока,
  • но это имеет смысл больше всего, когда Exception с на самом деле являются исключительными.

Из документов Python:

ЭСПЦ

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

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

for r in function():
    # process items

Пожалуйста, игнорируйте мое решение, если код, который я предоставляю, на первый взгляд неочевиден, и вы должны прочитать объяснение после примера кода.

Могу ли я предположить, что "значение не возвращено" означает, что возвращаемое значение равно None? Если да, или если "no value" является False в логическом смысле, вы можете сделать следующее, так как ваш код по существу обрабатывает "no value" как "not iterate":

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

Если function() возвращает что-то, что не соответствует действительности, вы перебираете пустой кортеж, т.е. вы не запускаете никаких итераций. Это по сути LBYL.

Как правило, у меня сложилось впечатление, что исключения должны быть зарезервированы для исключительных обстоятельств. Если 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

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

Что из следующего будет более предпочтительным и почему?

Смотри, прежде чем прыгать предпочтительнее в этом случае. При исключительном подходе ошибка TypeError может возникнуть в любом месте тела цикла, и она может быть захвачена и выброшена, а это не то, что вам нужно, и это затруднит отладку.

(Однако я согласен с Брэндоном Корфманом: возвращение None для 'no items' вместо пустого списка нарушено. Это неприятная привычка Java-кодеров, которую не следует видеть в Python. Или Java.)

bobince мудро указывает на то, что перенос второго регистра также может перехватывать TypeErrors в цикле, а это не то, что вам нужно. Если вы действительно хотите использовать попытку, вы можете проверить, повторяется ли она перед циклом

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

Как видите, это довольно некрасиво. Я не предлагаю это, но это должно быть упомянуто для полноты.

Что касается производительности, то использование блока try для кода, который обычно не вызывает исключений, быстрее, чем использование оператора if каждый раз. Итак, решение зависит от вероятности исключительных случаев.

Как общее практическое правило, вы никогда не должны использовать try/catch или другие средства обработки исключений для управления потоком. Хотя за кулисами итерация контролируется путем повышения StopIteration исключения, вы все равно должны предпочесть свой первый фрагмент кода второму.

Другие вопросы по тегам