Hasattr() против блока try-except для обработки несуществующих атрибутов

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

против

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

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

Ответ 1

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

Ответ 2

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

timeit it your friend

$ 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

Ответ 3

Я почти всегда использую 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 (issue9666) где hasattr теперь только ловит AttributeError.

Легкое обходное решение заключается в том, чтобы написать служебную функцию следующим образом:

_notset = object()

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

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

Ответ 4

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

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

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

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

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

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

  • Он короче try/finally и часто короче hasattr.

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

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

Ответ 9

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

Ответ 10

Первый.

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

Ответ 11

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

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

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

и

try: obj.attr
except: ...

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