if hasattr(obj, 'attribute'):
# do somthing
против
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Что следует предпочесть и почему?
Ответы:
hasattr
внутренне и быстро выполняет ту же задачу, что и try/except
блок: это очень специфический, оптимизированный, однозадачный инструмент, и поэтому его следует предпочитать, когда это применимо, альтернативе очень общего назначения.
hasattr
будут перехвачены все исключения. См. Мой ответ для примера и тривиального решения.
try
могу передать, что операция должна работать. Хотя try
намерение не всегда таково, оно распространено, поэтому его можно считать более читабельным.
Какие-нибудь скамейки, иллюстрирующие разницу в производительности?
время это твой друг
$ 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
try
примерно в два раза быстрее, чем hasattr()
. Если этого не происходит, try
это примерно в 1,5 раза медленнее, чем hasattr()
(и оба они значительно медленнее, чем если бы атрибут действительно существовал). Вероятно, это потому, что на счастливом пути try
почти ничего не делает (Python уже оплачивает накладные расходы на исключения независимо от того, используете ли вы их), но hasattr()
требует поиска имени и вызова функции. На неудачном пути они оба должны выполнить некоторую обработку исключений и a goto
, но hasattr()
делает это на C, а не на байт-коде Python.
Есть третья, часто лучшая альтернатива:
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, вам нужно будет использовать другое значение дозорного.
Я почти всегда использую 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
разобраться с ситуацией, а затем может вызвать соответствующее исключение.
hasattr
, по крайней мере, не будет ловить KeyboardInterrupt
и т. Д.
safehasattr
, чтобы просто getattr
скопировать значение в локальную переменную, если вы собираетесь ее использовать, как это почти всегда.
hasattr
это было так улучшено.
hasattr
, и пошел проверить. У нас было несколько забавных ошибок bzr, когда hasattr просто проглотил ^ C.
Я бы сказал, это зависит от того, может ли ваша функция принимать объекты без атрибута по дизайну , например, если у вас есть два вызывающих объекта, один из которых предоставляет объект с атрибутом, а другой - объект без него.
Если единственный случай, когда вы получите объект без атрибута, связан с какой-то ошибкой, я бы рекомендовал использовать механизм исключений, даже если он может быть медленнее, потому что я считаю, что это более чистый дизайн.
Итог: я думаю, что это проблема дизайна и удобочитаемости, а не проблема эффективности.
Если нет атрибута не является условием ошибки, вариант обработки исключений имеет проблему: он также может перехватить AttributeErrors, которые могут возникнуть внутри при доступе к obj.attribute (например, потому что атрибут является свойством, поэтому доступ к нему вызывает некоторый код).
Эта тема была затронута в докладе Себастьяна Витовски на EuroPython 2016 Написание более быстрого Python . Вот репродукция его слайда с итогами работы. Он также использует терминологию, прежде чем перейти к этому обсуждению, и стоит упомянуть здесь, чтобы пометить это ключевое слово.
Если атрибут действительно отсутствует, то просьба о прощении будет медленнее, чем запрос разрешений. Таким образом, вы можете использовать способ запроса разрешения, если знаете, что очень вероятно, что атрибут будет отсутствовать или другие проблемы, которые вы можете предсказать. В противном случае, если вы ожидаете, что код будет в большинстве случаев читабельным
# 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
Я бы предложил вариант 2. Вариант 1 имеет состояние гонки, если какой-то другой поток добавляет или удаляет атрибут.
Также у python есть идиома , что EAFP («проще просить прощения, чем разрешение») лучше, чем LBYL («посмотри, прежде чем прыгнуть»).
С практической точки зрения, на большинстве языков использование условного выражения всегда будет значительно быстрее, чем обработка исключения.
Если вы хотите обработать случай, когда атрибут не существует где-то за пределами текущей функции, исключение - лучший способ. Индикатор того, что вы можете захотеть использовать исключение вместо условного, заключается в том, что условное выражение просто устанавливает флаг и прерывает текущую операцию, а что-то в другом месте проверяет этот флаг и предпринимает действия на основе этого.
Тем не менее, как указывает Ракс Ольгуд, общение с другими людьми является одним из важных атрибутов кода, и то, что вы хотите сказать, сказав «это исключительная ситуация», а не «это то, что я ожидаю произойти» может быть более важным. .
Первый.
Короче лучше. Исключения должны быть исключительными.
for
оператора, и оно hasattr
тоже используется. Тем не менее, «короче - лучше» (и «проще - лучше»!) ДЕЙСТВИТЕЛЬНО применяется, поэтому более простой, короткий и конкретный hasattr действительно предпочтительнее.
По крайней мере, когда дело касается только того, что происходит в программе, без учета человеческой части читабельности и т. Д. (Что на самом деле в большинстве случаев более важно, чем производительность (по крайней мере, в этом случае - с таким диапазоном производительности), как указали Рои Адлер и другие).
Тем не менее, если посмотреть на это с этой точки зрения, тогда возникает вопрос выбора между
try: getattr(obj, attr)
except: ...
и
try: obj.attr
except: ...
поскольку hasattr
для определения результата используется только первый случай. Пища для размышлений ;-)