if hasattr(obj, 'attribute'):
# do somthing
проти
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Кому слід віддати перевагу і чому?
Відповіді:
hasattr
внутрішньо і швидко виконує те саме завдання, що і try/except
блок: це дуже специфічний, оптимізований інструмент із одним завданням, і тому, коли це можливо, слід віддавати перевагу альтернативі загального призначення.
hasattr
буде враховано всі винятки в Python 2.x. Див. Мою відповідь для прикладу та тривіального обхідного шляху.
try
можна сказати, що операція повинна спрацювати. Хоча try
намір не завжди є таким, він є загальним, тому його можна вважати більш читабельним.
Будь-які лавки, що ілюструють різницю у продуктивності?
timeit це твій друг
$ 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
не має поганої поведінки при ковтанні винятків, на яку звернув увагу Мартін Гайзер - у старих Пітонах hasattr
навіть проковтне a 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 ( випуск 9666 ), де 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 має перегоновий стан, якщо якийсь інший потік додає або видаляє атрибут.
Також пітон має ідіома , що ЕСПЦ ( «простіше попросити вибачення , ніж дозволу») краще , ніж LBYL ( «дивитися , перш ніж стрибати»).
З практичної точки зору, у більшості мов використання умовної умови завжди буде неймовірно швидшим, ніж обробка винятків.
Якщо ви хочете обробляти випадок атрибута, який не існує десь поза поточною функцією, виняток - кращий шлях. Індикатором того, що вам може знадобитися використовувати виняток замість умовного, є те, що умовний просто встановлює прапор і перериває поточну операцію, а щось інше перевіряє цей прапор і вживає дії на основі цього.
Тим не менш, як зазначає Ракс Олгуд, спілкування з іншими є одним з важливих атрибутів коду, і те, що ви хочете сказати, сказавши "це виняткова ситуація", а не "це те, що я очікую, щоб сталося", може бути більш важливим .
Перший.
Краще - краще. Винятки мають бути винятковими.
for
твердження є hasattr
один, і він теж використовує один. Однак "коротше - це краще" (і "простіше - краще"!) ЗАСТОСОВУЙТЕ, тому простіший, коротший, більш конкретний hasattr дійсно є кращим.
Принаймні, коли це залежить від того, що відбувається в програмі, не враховуючи людської частини читабельності тощо (що насправді більшу частину часу є важливішим за продуктивність (принаймні в цьому випадку - з таким періодом продуктивності), як вказували Рої Адлер та інші).
Проте, дивлячись на це з цієї точки зору, це стає питанням вибору між ними
try: getattr(obj, attr)
except: ...
і
try: obj.attr
except: ...
оскільки hasattr
просто використовує перший випадок для визначення результату. Їжа для роздумів ;-)