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


151

Есть ли смысл решить, какую из конструкций 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, вам нужно обернуть другой блок try: except:. В этом конкретном примере я бы просто использовал if:
— richo

Ответы:


251

Вы часто слышите, что 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:

EAFP

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


1
Спасибо. Я вижу, что в этом случае обоснованием может быть ожидание результата.
— artdanil 02

6
.... и это одна (из многих) причин, почему так сложно выполнить настоящую оптимизирующую JIT для Python. Как доказывает недавно выпущенная бета-версия LuaJIT 2, динамические языки могут быть действительно очень быстрыми; но это сильно зависит от исходного языкового дизайна и стиля, который он поощряет. (кстати, из-за языкового дизайна даже самые лучшие JIT JavaScirpt не могут сравниться с LuaJIT 1, не говоря уже о 2)
— Хавьер

1
@ 2rs2ts: Я сам делал подобные тайминги. В Python 3 это try/exceptбыло на 25% быстрее, чем if key in d:в случаях, когда ключ был в словаре. Это было намного медленнее, когда ключа не было в словаре, как ожидалось и соответствовало этому ответу.
— Tim Pietzcker 06

5
Однако я знаю, что это старый ответ: использование таких операторов, как 1/1with timeit, не лучший выбор, потому что они будут оптимизированы (см. dis.dis('1/1')И обратите внимание, что деления не происходит).
— Андреа Корбеллини,

1
Это также зависит от операции, которую вы хотите провести. Одно деление выполняется быстро, а обнаружение ошибки обходится дорого. Но даже в тех случаях, когда стоимость обработки исключений приемлема, внимательно подумайте о том, что вы просите компьютер сделать. Если сама операция стоит очень дорого, независимо от ее результатов, и есть дешевый способ узнать, возможен ли успех: используйте его. Пример: не копируйте половину файла только для того, чтобы понять, что места недостаточно.
— Bachsau

15

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

for r in function():
    # process items

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

2
@artdanil: Итак, вы можете обернуть эту функцию в одну, которую вы пишете, которая работает как та, о которой думает Брэндон Корфман.
— quamrana 02

24
что вызывает вопрос: должна ли оболочка использовать if или попытку обработать не повторяющийся случай?
— jcdyer 03

@quamrana, что я и пытаюсь сделать. Итак, как указал @jcd, вопрос в том, как я должен обрабатывать ситуацию в функции-оболочке.
— artdanil 03

12

Не обращайте внимания на мое решение, если предоставленный мной код на первый взгляд неочевиден и вам необходимо прочитать объяснение после примера кода.

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

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

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


4

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


4

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

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

(Я согласен с Брэндоном Корфманом: возврат None вместо «нет элементов» вместо пустого списка нарушен. Это неприятная привычка программистов 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 02

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, если ничего не найдено, потому что сам поиск завершился без ошибок.
— Bachsau

1

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


-5

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


7
Это верно для Java. Python довольно сильно поддерживает EAFP.
— fengb 02

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