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

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

vs

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

Что следует предпочесть и почему?


person Imran    schedule 24.05.2009    source источник
comment
Дубликат: stackoverflow.com/questions/598157/, stackoverflow.com/questions/610883/   -  person Paolo Bergantino    schedule 24.05.2009


Ответы (12)


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

person Alex Martelli    schedule 24.05.2009
comment
За исключением того, что вам все еще нужен блок try / catch для обработки условий гонки (если вы используете потоки). - person Douglas Leeder; 24.05.2009
comment
Или особый случай, с которым я только что столкнулся: django OneToOneField без значения: hasattr (obj, field_name) возвращает False, но есть атрибут с field_name: он просто вызывает ошибку DoesNotExist. - person Matthew Schinckel; 25.02.2011
comment
Обратите внимание, что hasattr перехватит все исключения в Python 2.x. См. мой ответ для примера и тривиального обходного пути. - person Martin Geisler; 24.04.2013
comment
Интересный комментарий: try может передать, что операция должна работать. Хотя намерение try не всегда таково, оно распространено, поэтому его можно считать более читабельным. - person Ioannis Filippidis; 01.02.2014

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

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

$ 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
person ZeD    schedule 24.05.2009
comment
+1 за предоставление интересных, осязаемых цифр. Фактически, попытка эффективна, когда она содержит общий случай (т.е. когда исключение Python действительно является исключительным). - person Eric O Lebigot; 24.05.2009
comment
Я не знаю, как интерпретировать эти результаты. Что здесь быстрее и насколько? - person Stevoisiak; 27.03.2018
comment
@ StevenM.Vascellaro: Если атрибут существует, try примерно в два раза быстрее, чем hasattr(). В противном случае try примерно в 1,5 раза медленнее, чем hasattr() (и оба они существенно медленнее, чем если бы атрибут действительно существовал). Вероятно, это потому, что на счастливом пути try почти ничего не делает (Python уже оплачивает накладные расходы на исключения независимо от того, используете ли вы их), но hasattr() требует поиска имени и вызова функции. На неудачном пути им обоим приходится выполнять некоторую обработку исключений и goto, но hasattr() делает это на C, а не на байт-коде Python. - person Kevin; 06.12.2018

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

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 установлено значение None, вам нужно будет использовать другое значение дозорного.

person poolie    schedule 24.05.2013
comment
+1 - это связано с dict.get ('my_key', 'default_value') и должно быть более широко известно о - person ; 11.05.2014
comment
Отлично подходит для общего случая использования, когда вы хотите проверить наличие и использовать атрибут со значением по умолчанию. - person dsalaj; 23.11.2015
comment
Нет необходимости в if attr is not None:. Достаточно только if attr:. - person demberto; 04.03.2021
comment
In [1]: class Foo(): ...: def foo(self): ...: return getattr(self, 'x', False) ...: In [2]: f = Foo() In [3]: f.foo() Out[3]: False In [4]: f.x = 1 In [5]: f.foo() Out[5]: 1 великолепно! - person januszm; 10.06.2021

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

person Martin Geisler    schedule 24.04.2013
comment
Это также было немного улучшено в Python2.6, так что hasattr по крайней мере не поймать KeyboardInterrupt и т. д. - person poolie; 24.05.2013
comment
Или, вместо safehasattr, просто используйте getattr для копирования значения в локальную переменную, если вы собираетесь ее использовать, что вы почти всегда делаете. - person poolie; 24.05.2013
comment
@poolie Это хорошо, я не знал, что hasattr был таким образом улучшен. - person Martin Geisler; 24.05.2013
comment
Да, это хорошо. Я не знал этого до сегодняшнего дня, когда собирался сказать кому-то избегать hasattr, и пошел проверить. У нас было несколько забавных ошибок bzr, когда hasattr просто проглотил ^ C. - person poolie; 24.05.2013
comment
Возникла проблема при обновлении 2.7 до 3.6. Этот ответ поможет мне понять и решить проблему. - person Kamesh Jungi; 11.12.2019

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

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

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

person Roee Adler    schedule 24.05.2009
comment
+1 за то, что настаивают на том, зачем пытаться, имеет значение для людей, читающих код. :) - person Eric O Lebigot; 24.05.2009

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

person UncleZeiv    schedule 30.06.2009
comment
На мой взгляд, это серьезная проблема, которая в значительной степени игнорировалась. - person Rick supports Monica; 21.06.2018

Эта тема была затронута в докладе на 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
person jxramos    schedule 16.03.2018

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

person n8gray    schedule 24.05.2009

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

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

person Douglas Leeder    schedule 24.05.2009

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

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

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

person cjs    schedule 24.05.2009
comment
+1 за то, что настаивает на том, что try можно интерпретировать как исключительную ситуацию по сравнению с условным тестом. :) - person Eric O Lebigot; 24.05.2009

Первое.

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

person Unknown    schedule 24.05.2009
comment
Исключения очень распространены в Python - по одному в конце каждого оператора for, и hasattr тоже его использует. Тем не менее, чем короче, тем лучше (а проще - лучше!) ДЕЙСТВИТЕЛЬНО применяется, поэтому более простой, короткий и конкретный hasattr действительно предпочтительнее. - person Alex Martelli; 24.05.2009
comment
@Alex только потому, что синтаксический анализатор Python преобразует эти операторы в 1, не означает, что он очень распространен. Есть причина, по которой они сделали этот синтаксический сахар: чтобы вы не зацикливались на бессмысленности набора try except block. - person Unknown; 24.05.2009
comment
Если исключение является исключительным, то явное лучше, а второй вариант исходного плаката лучше, я бы сказал ... - person Eric O Lebigot; 24.05.2009

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

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

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

и

try: obj.attr
except: ...

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

person Levite    schedule 12.10.2012