Прощение Python против Разрешения и Утиной Печати

44

В Python я часто слышу, что лучше «просить прощения» (перехват исключений), а не «спрашивать разрешение» (проверка типа / условия). Что касается принудительного набора утки в Python, это

try:
    x = foo.bar
except AttributeError:
    pass
else:
    do(x)

лучше или хуже чем

if hasattr(foo, "bar"):
    do(foo.bar)
else:
    pass

с точки зрения производительности, читабельности, «питона» или какого-то другого важного фактора?

darkfeline
источник
17
есть третий вариант, ничего не делать и относиться к любому foo без бара как к ошибке
jk.
Я помню, что слышал, что hasattrон реализован именно с этой попытки. Не уверен, если это правда ... (это будет действовать по-другому на свойства, не так ли? Может быть, я думаю о getattr..)
Изката
@Izkata: Реализацияhasattr действительно использует эквивалент C-API getattr(возврат в Trueслучае успеха, Falseесли нет), но обработка исключений в C происходит намного быстрее.
Мартейн Питерс
2
Я принял ответ martijn, но я хотел бы добавить, что если вы пытаетесь установить атрибут, вам определенно следует рассмотреть использование try / catch, потому что это может быть свойство без установщика, и в этом случае hasattr будет истинным , но он все равно вызовет AttributeError.
darkfeline

Ответы:

60

Это действительно зависит от того, как часто вы думаете, что будет выдано исключение.

Оба подхода, на мой взгляд, одинаково верны, по крайней мере, с точки зрения читабельности и питонности. Но если 90% ваших объектов не имеют атрибута, barвы заметите явную разницу в производительности между двумя подходами:

>>> import timeit
>>> def askforgiveness(foo=object()):
...     try:
...         x = foo.bar
...     except AttributeError:
...         pass
... 
>>> def askpermission(foo=object()):
...     if hasattr(foo, 'bar'):
...         x = foo.bar
... 
>>> timeit.timeit('testfunc()', 'from __main__ import askforgiveness as testfunc')
2.9459929466247559
>>> timeit.timeit('testfunc()', 'from __main__ import askpermission as testfunc')
1.0396890640258789

Но если 90% ваших объектов действительно имеют атрибут, таблицы были повернуты:

>>> class Foo(object):
...     bar = None
... 
>>> foo = Foo()
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askforgiveness as testfunc, foo')
0.31336188316345215
>>> timeit.timeit('testfunc(foo)', 'from __main__ import askpermission as testfunc, foo')
0.4864199161529541

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

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

Мартейн Питерс
источник
1
Несколько недель назад я задал этот вопрос: programmers.stackexchange.com/questions/161798/… Там я спросил, нужно ли работать со свободно типизированными языками дополнительной проверкой типов, и меня засыпали люди, которые говорили, что нет. Знай, я вижу, у тебя есть.
Тулаинс Кордова
@ user1598390: Когда вы определяете API, который ожидает однородное сочетание типов, вы должны выполнить несколько тестов. Большую часть времени вы этого не делаете. Боюсь, это специфическая область, из которой нельзя вывести правила о парадигмах в целом.
Мартейн Питерс
Ну, любая серьезная разработка системы включает в себя определение API. Так что я думаю, что строгие языки лучше всего подходят для этого, потому что вам нужно меньше кодировать, так как компилятор проверяет типы во время компиляции.
Тулаинс Кордова
1
@GarethRees: он устанавливает образец для второй половины ответа, где я передаю аргумент для тестируемой функции.
Мартейн Питерс
1
Обратите внимание, что for hasattr, он на самом деле делает C-api эквивалентом try-исключений, в любом случае, так как оказывается, что единственный общий способ определить, есть ли у объекта атрибут в Python, - попытаться получить к нему доступ.
user2357112 поддерживает Monica
11

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

Как и во всех вопросах производительности, единственный способ убедиться в этом - профилировать ваш код. Напишите обе версии и посмотрите, какая из них работает быстрее. Хотя, по моему опыту, «путь Python», как правило, самый быстрый путь.

tylerl
источник
3

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

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

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

orip
источник
-1

С точки зрения корректности , я думаю, что обработка исключений - это правильный путь (хотя иногда я сам использую подход hasattr ()). Основная проблема, связанная с использованием функции hasattr (), заключается в том, что она превращает нарушения кодовых контрактов в скрытые сбои (это большая проблема в JavaScript, который не создает несуществующие свойства).

Джо
источник
3
Ваш ответ, кажется, не добавляет ничего сверх того, что уже было сказано другими. В отличие от других сайтов, программисты ожидают ответов, объясняющих причину ответа. Вы затрагиваете хороший момент с проблемой молчаливого сбоя, но не должны отдавать должное этому. Чтение следующего может помочь: programmers.stackexchange.com/help/how-to-answer
Последний ответ, который я сделал, был раскритикован за то, что он был слишком широким. Я думал, что попробую коротко и кратко.
Джо