hasattr() против блока try-Кроме того, чтобы иметь дело с несуществующими атрибутами

if hasattr(obj, 'attribute'):
    # do somthing

против

try:
    # access obj.attribute
except AttributeError, e:
    # deal with AttributeError

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

13 ответов

Решение

hasattr внутренне и быстро выполняет ту же задачу, что и try/except блок: это очень специфический, оптимизированный инструмент для одной задачи, и поэтому его следует отдавать предпочтение, когда это применимо, альтернативе очень общего назначения.

Есть скамейки, которые иллюстрируют разницу в производительности?

время это твой друг

$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "nonexistent")'
1000000 loops, best of 3: 1.87 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'hasattr(c, "a")'
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.a
except:
 pass'
1000000 loops, best of 3: 0.247 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()' 'try:
 c.nonexistent
except:
 pass'
100000 loops, best of 3: 3.13 usec per loop
$

       |positive|negative
hasattr|  0.446 |  1.87 
try    |  0.247 |  3.13

Существует третья, а часто и лучшая альтернатива:

attr = getattr(obj, 'attribute', None)
if attr is not None:
     print attr

Преимущества:

  1. getattr не имеет плохого поведения при проглатывании исключений, на которое указал Мартин Гейзер - в старых питонах hasattr даже проглотит KeyboardInterrupt,

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

  3. Атрибут считывается атомарно и защищен от других потоков, изменяющих объект. (Хотя, если это серьезная проблема, вы можете подумать о блокировке объекта перед тем, как получить к нему доступ.)

  4. Короче чем try/finally и часто короче hasattr,

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

  6. Доступ к атрибуту медленнее, чем к локальной переменной (особенно если это не простой атрибут экземпляра). (Хотя, если честно, микрооптимизация в Python часто является глупым поручением.)

Остерегайтесь одной вещи, если вам небезразличен случай, когда obj.attribute Нет, вам нужно использовать другое значение часового.

Я почти всегда использую hasattr: это правильный выбор для большинства случаев.

Проблемный случай, когда класс переопределяет __getattr__: hasattr будет ловить все исключения вместо того, чтобы ловить только AttributeError как вы ожидаете. Другими словами, код ниже будет печатать b: False хотя было бы более уместно увидеть ValueError исключение:

class X(object):
    def __getattr__(self, attr):
        if attr == 'a':
            return 123
        if attr == 'b':
            raise ValueError('important error from your database')
        raise AttributeError

x = X()
print 'a:', hasattr(x, 'a')
print 'b:', hasattr(x, 'b')
print 'c:', hasattr(x, 'c')

Таким образом, важная ошибка исчезла. Это было исправлено в Python 3.2 ( выпуск 9666), где hasattr сейчас только ловит AttributeError,

Простой обходной путь - написать вспомогательную функцию следующим образом:

_notset = object()

def safehasattr(thing, attr):
    return getattr(thing, attr, _notset) is not _notset

Это давайте getattr разобраться с ситуацией, и тогда может возникнуть соответствующее исключение.

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

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

Итог: я думаю, что это проблема дизайна и читабельности, а не эффективности.

Эта тема была освещена в докладе EuroPython 2016 Пишем быстрее Python Себастьяном Витовски. Вот репродукция его слайда с кратким описанием. Он также использует терминологический взгляд, прежде чем вы перейдете к этой дискуссии, стоит упомянуть здесь, чтобы пометить это ключевое слово.

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

3 РАЗРЕШЕНИЯ ИЛИ ПРОЩЕНИЕ?

# CASE 1 -- Attribute Exists
class Foo(object):
    hello = 'world'
foo = Foo()

if hasatter(foo, 'hello'):
    foo.hello
## 149ns ##

try:
    foo.hello
except AttributeError:
    pass
## 43.1 ns ##
## 3.5 times faster


# CASE 2 -- Attribute Absent
class Bar(object):
    pass
bar = Bar()

if hasattr(bar, 'hello'):
    bar.hello
## 428 ns ##

try:
    bar.hello
except AttributeError :
    pass
## 536 ns ##
## 25% slower

Если отсутствие атрибута не является условием ошибки, у варианта обработки исключительной ситуации есть проблема: он будет также отлавливать AttributeErrors, которые могут возникать изнутри при доступе к obj.attribute (например, потому что атрибут является свойством, так что доступ к нему вызывает некоторый код).

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

Я бы предложил вариант 2. Вариант 1 имеет условие гонки, если какой-то другой поток добавляет или удаляет атрибут.

Также у python есть идиома, что EAFP ("проще просить прощения, чем разрешения") лучше, чем LBYL ("смотри, прежде чем прыгнуть").

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

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

Тем не менее, как указывает Ракс Олгуд, общение с другими людьми является одним из важных атрибутов кода, и то, что вы хотите сказать, говоря "это исключительная ситуация", а не "это то, чего я ожидаю", может быть более важным,

Первый.

Чем короче, тем лучше. Исключения должны быть исключительными.

По крайней мере, когда дело доходит только до того, что происходит в программе, исключая человеческую часть читабельности и т. Д. (Что на самом деле в большинстве случаев более важно, чем производительность (по крайней мере, в этом случае - с таким диапазоном производительности), как указал Рои Адлер и другие).

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

try: getattr(obj, attr)
except: ...

а также

try: obj.attr
except: ...

поскольку hasattr просто использует первый случай, чтобы определить результат. Пища для размышлений;-)

Try/Except может быть выбором, когда hasattr() является триггером.

Если вы используетедекоратор, предоставляемый functools, бывают случаи, когда вы не хотите использовать какой-либо поиск атрибутов, чтобы избежать упреждающего выполнения потенциально дорогостоящего выполнения (если он еще не существует как атрибут), а просто попытаться удалить его. Например, если вам нужно убедиться, что он не был найден (и, следовательно, не превращен в атрибут) с использованием других атрибутов, которые не были готовы - если он не существует как атрибут, это означает, что вы не можете удалить это и это круто - и если оно существует, вы наверняка захотите его удалить.

      if hasattr(obj,attr):
    # this will do a lookup and in case of @cached_property execute unless
    # the attribute already exists
    # not to speak about potential race conditions
    ...
    

try:
     del(self.attr)
except AttributeError:
     # ... we're happy
     # 
    
Другие вопросы по тегам