hasattr () проти блоку try-osim для роботи з неіснуючими атрибутами


Відповіді:


82

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


8
За винятком того, що вам все ще потрібен блок try / catch для обробки умов перегонів (якщо ви використовуєте потоки).
Дуглас Лідер,

1
Або, особливий випадок, з яким я щойно зіткнувся: django OneToOneField без значення: hasattr (obj, field_name) повертає False, але є атрибут з field_name: він просто викликає помилку DoesNotExist.
Matthew Schinckel

3
Зверніть увагу, що hasattrбуде враховано всі винятки в Python 2.x. Див. Мою відповідь для прикладу та тривіального обхідного шляху.
Мартін Гейслер

5
Цікавий коментар : tryможна сказати, що операція повинна спрацювати. Хоча tryнамір не завжди є таким, він є загальним, тому його можна вважати більш читабельним.
Іоанніс Філіппідіс

88

Будь-які лавки, що ілюструють різницю у продуктивності?

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

16
+1 за надання цікавих, відчутних цифр. Насправді "спробувати" ефективно, коли воно містить загальний випадок (тобто коли виняток Python справді винятковий).
Ерік Лебіго

Я не знаю, як інтерпретувати ці результати. Що тут швидше, і на скільки?
Стевойсяк,

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не має поганої поведінки при ковтанні винятків, на яку звернув увагу Мартін Гайзер - у старих Пітонах hasattrнавіть проковтне a 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 ( випуск 9666 ), де 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. Ця відповідь допоможе мені зрозуміти та вирішити проблему.
Kamesh Jungi

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

4

Якщо ви перевіряєте лише один атрибут, я б сказав використовувати hasattr. Однак, якщо ви робите кілька доступів до атрибутів, які можуть існувати, а можуть і не існувати, тоді використання tryблоку може заощадити вам набравши текст.


3

Я б запропонував варіант 2. Варіант 1 має перегоновий стан, якщо якийсь інший потік додає або видаляє атрибут.

Також пітон має ідіома , що ЕСПЦ ( «простіше попросити вибачення , ніж дозволу») краще , ніж LBYL ( «дивитися , перш ніж стрибати»).


2

З практичної точки зору, у більшості мов використання умовної умови завжди буде неймовірно швидшим, ніж обробка винятків.

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

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


+1 за наполягання на тому, що "спробувати" можна інтерпретувати як "це виняткова ситуація", порівняно з умовним тестом. :)
Ерік Лебіго

0

Перший.

Краще - краще. Винятки мають бути винятковими.


5
Винятки дуже поширені в Python - у кінці кожного forтвердження є hasattrодин, і він теж використовує один. Однак "коротше - це краще" (і "простіше - краще"!) ЗАСТОСОВУЙТЕ, тому простіший, коротший, більш конкретний hasattr дійсно є кращим.
Алекс Мартеллі,

@Alex те, що синтаксичний аналізатор Python перетворює ці твердження на 1, не означає, що він дуже поширений. Існує причина, чому вони зробили цей синтаксичний цукор: отже, ви не застрягли в грубості набору спроби, окрім блоку.
Невідомо

Якщо виняток є винятковим, тоді "явно краще", а оригінальний другий варіант кращий, я б сказав ...
Ерік Лебіго

0

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

Проте, дивлячись на це з цієї точки зору, це стає питанням вибору між ними

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

і

try: obj.attr
except: ...

оскільки hasattrпросто використовує перший випадок для визначення результату. Їжа для роздумів ;-)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.