hasattr () vs блок try-except для работы с несуществующими атрибутами

Ответы:

82

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

Алекс Мартелли
источник
8
За исключением того, что вам все еще нужен блок try / catch для обработки условий гонки (если вы используете потоки).
Дуглас Лидер,
1
Или особый случай, с которым я только что столкнулся: django OneToOneField без значения: hasattr (obj, field_name) возвращает False, но есть атрибут с field_name: он просто вызывает ошибку DoesNotExist.
Мэтью Шинкель
3
Обратите внимание, что в Python 2.x hasattrбудут перехвачены все исключения. См. Мой ответ для примера и тривиального решения.
Мартин Гейслер
5
Интересный комментарий : tryмогу передать, что операция должна работать. Хотя tryнамерение не всегда таково, оно распространено, поэтому его можно считать более читабельным.
Иоаннис Филиппидис
88

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

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

$ 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
ZeD
источник
16
+1 за предоставление интересных, ощутимых цифр. Фактически, «попытка» эффективна, когда она содержит общий случай (т.е. когда исключение Python действительно является исключительным).
Эрик О Лебигот,
Я не знаю, как интерпретировать эти результаты. Что здесь быстрее и на сколько?
Stevoisiak
2
@ StevenM.Vascellaro: Если атрибут существует, tryпримерно в два раза быстрее, чем hasattr(). Если этого не происходит, tryэто примерно в 1,5 раза медленнее, чем hasattr()(и оба они значительно медленнее, чем если бы атрибут действительно существовал). Вероятно, это потому, что на счастливом пути tryпочти ничего не делает (Python уже оплачивает накладные расходы на исключения независимо от того, используете ли вы их), но hasattr()требует поиска имени и вызова функции. На неудачном пути они оба должны выполнить некоторую обработку исключений и a goto, но hasattr()делает это на C, а не на байт-коде Python.
Кевин
24

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

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

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

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

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

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

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

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

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

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

пулли
источник
1
+1 - Это связано с dict.get ('my_key', 'default_value') и должно быть более широко известно
1
Отлично подходит для общего случая использования, когда вы хотите проверить наличие и использовать атрибут со значением по умолчанию.
dsalaj
18

Я почти всегда использую 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разобраться с ситуацией, а затем может вызвать соответствующее исключение.

Мартин Гейслер
источник
2
Это также было немного улучшено в Python2.6, так что hasattr, по крайней мере, не будет ловить KeyboardInterruptи т. Д.
poolie
Или, вместо того safehasattr, чтобы просто getattrскопировать значение в локальную переменную, если вы собираетесь ее использовать, как это почти всегда.
poolie
@poolie Это хорошо, я не знал, что hasattrэто было так улучшено.
Мартин Гейслер
Да, это хорошо. Я не знал этого до сегодняшнего дня, когда собирался сказать кому-то, чтобы он избегал hasattr, и пошел проверить. У нас было несколько забавных ошибок bzr, когда hasattr просто проглотил ^ C.
poolie
Возникла проблема при обновлении 2.7 до 3.6. Этот ответ поможет мне понять и решить проблему.
Камеш
13

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

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

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

Рои Адлер
источник
1
+1 за то, что настаивает на том, почему «попробовать» имеет значение для людей, читающих код. :)
Эрик О Лебигот
5

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

ДядяЗеив
источник
На мой взгляд, это серьезная проблема, которая в значительной степени игнорируется.
Рик поддерживает Монику
5

Эта тема была затронута в докладе Себастьяна Витовски на 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
Jxramos
источник
4

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

n8gray
источник
3

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

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

Дуглас Лидер
источник
2

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

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

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

cjs
источник
+1 за то, что настаивает на том, что «попытка» может быть истолкована как «это исключительная ситуация» по сравнению с условным тестом. :)
Эрик О Лебигот
0

Первый.

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

Неизвестно
источник
5
Исключения очень распространены в Python - одно в конце каждого forоператора, и оно hasattrтоже используется. Тем не менее, «короче - лучше» (и «проще - лучше»!) ДЕЙСТВИТЕЛЬНО применяется, поэтому более простой, короткий и конкретный hasattr действительно предпочтительнее.
Alex Martelli
@Alex только потому, что парсер Python преобразует эти операторы в 1, не означает, что он очень распространен. Есть причина, по которой они сделали этот синтаксический сахар: чтобы вы не застряли с трудностями набора try except block.
Неизвестный
Если исключение является исключительным, то «явное - лучше», а второй вариант оригинального плаката лучше, я бы сказал…
Эрик О Лебигот,
0

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

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

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

и

try: obj.attr
except: ...

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

Левит
источник