Есть ли смысл решить, какую из конструкций 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 поощряет стиль 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
s на самом деле исключительны.Из документов Python:
источник
try/except
было на 25% быстрее, чемif key in d:
в случаях, когда ключ был в словаре. Это было намного медленнее, когда ключа не было в словаре, как ожидалось и соответствовало этому ответу.1/1
with timeit, не лучший выбор, потому что они будут оптимизированы (см.dis.dis('1/1')
И обратите внимание, что деления не происходит).Ваша функция не должна возвращать смешанные типы (например, список или пустую строку). Он должен возвращать список значений или просто пустой список. Тогда вам не нужно будет что-либо тестировать, то есть ваш код сворачивается:
for r in function(): # process items
источник
Не обращайте внимания на мое решение, если предоставленный мной код на первый взгляд неочевиден и вам необходимо прочитать объяснение после примера кода.
Могу ли я предположить, что «значение не возвращено» означает, что возвращаемое значение - None? Если да, или если «нет значения» имеет логическое значение False, вы можете сделать следующее, поскольку ваш код по существу обрабатывает «нет значения» как «не повторять»:
for r in function() or (): # process items
Если
function()
возвращает что-то, что не соответствует истине, вы перебираете пустой кортеж, то есть не выполняете никаких итераций. По сути, это LBYL.источник
Ваш второй пример не работает - код никогда не вызовет исключение TypeError, поскольку вы можете выполнять итерацию как по строкам, так и по спискам. Итерация по пустой строке или списку также допустима - тело цикла будет выполнено нулевое количество раз.
источник
В этом случае предпочтительнее смотреть перед прыжком. При исключительном подходе ошибка TypeError может возникнуть где угодно в теле цикла, и ее поймают и выбросят, а это не то, что вам нужно, и это усложнит отладку.
(Я согласен с Брэндоном Корфманом: возврат None вместо «нет элементов» вместо пустого списка нарушен. Это неприятная привычка программистов Java, которая не должна встречаться в Python или Java.)
источник
В общем, у меня сложилось впечатление, что исключения следует делать на исключительные обстоятельства. Если
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
источник
++
не работает в python, используйте+= 1
вместо этого.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, если ничего не найдено, потому что сам поиск завершился без ошибок.Что касается производительности, использование блока try для кода, который обычно не вызывает исключений, быстрее, чем использование оператора if каждый раз. Итак, решение зависит от вероятности исключительных случаев.
источник
Как правило, вы никогда не должны использовать try / catch или какие-либо средства обработки исключений для управления потоком. Несмотря на то, что скрытая итерация контролируется путем создания
StopIteration
исключений, вы все равно должны предпочесть свой первый фрагмент кода второму.источник