hasattr () vs blok try-except, aby poradzić sobie z nieistniejącymi atrybutami

Odpowiedzi:

82

hasattrwewnętrznie i szybko wykonuje to samo zadanie co try/exceptblok: jest to bardzo specyficzne, zoptymalizowane narzędzie do jednego zadania i dlatego powinno być preferowane, gdy ma to zastosowanie, zamiast bardzo ogólnego przeznaczenia.

Alex Martelli
źródło
8
Z wyjątkiem tego, że nadal potrzebujesz bloku try / catch do obsługi warunków wyścigu (jeśli używasz wątków).
Douglas Leeder
1
Albo przypadek specjalny, na który właśnie natknąłem się: django OneToOneField bez wartości: hasattr (obj, field_name) zwraca False, ale istnieje atrybut z field_name: po prostu zgłasza błąd DoesNotExist.
Matthew Schinckel,
3
Zwróć uwagę, że hasattrspowoduje to przechwycenie wszystkich wyjątków w Pythonie 2.x. Zobacz moją odpowiedź na przykład i trywialne obejście.
Martin Geisler
5
Ciekawy komentarz : trymoże przekazać, że operacja powinna działać. Chociaż tryzamiar nie zawsze jest taki, jest powszechny, więc można go uznać za bardziej czytelny.
Ioannis Filippidis
88

Jakieś ławki, które ilustrują różnicę w wydajności?

czas to twój przyjaciel

$ 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
źródło
16
+1 za podanie interesujących, namacalnych liczb. W rzeczywistości „try” jest wydajne, gdy zawiera typowy przypadek (tj. Gdy wyjątek Pythona jest naprawdę wyjątkowy).
Eric O Lebigot
Nie jestem pewien, jak zinterpretować te wyniki. Co jest tutaj szybsze io ile?
Stevoisiak
2
@ StevenM.Vascellaro: Jeśli atrybut istnieje, tryjest około dwa razy szybszy niż hasattr(). Jeśli tak nie tryjest , jest około 1,5 raza wolniejsze niż hasattr()(i oba są znacznie wolniejsze niż wtedy, gdy atrybut istnieje). Dzieje się tak prawdopodobnie dlatego, że na szczęśliwej ścieżce tryprawie nic nie robi (Python już płaci za narzut wyjątków, niezależnie od tego, czy ich używasz), ale hasattr()wymaga wyszukiwania nazwy i wywołania funkcji. Na nieszczęśliwej ścieżce obaj muszą wykonać jakąś obsługę wyjątków i a goto, ale hasattr()robią to w C, a nie w kodzie bajtowym Pythona.
Kevin
24

Istnieje trzecia i często lepsza alternatywa:

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

Zalety:

  1. getattrnie ma złego zachowania polegającego na połykaniu wyjątków, wskazanego przez Martina Geisera - w starych Pythonach hasattrnawet połknie plik KeyboardInterrupt.

  2. Normalnym powodem, dla którego sprawdzasz, czy obiekt ma atrybut, jest to, że możesz użyć atrybutu, a to naturalnie prowadzi do tego.

  3. Atrybut jest odczytywany atomowo i jest bezpieczny dla innych wątków zmieniających obiekt. (Chociaż, jeśli jest to poważny problem, możesz rozważyć zablokowanie obiektu przed uzyskaniem do niego dostępu).

  4. Jest krótszy try/finallyi często krótszy niż hasattr.

  5. Szeroki except AttributeErrorblok może złapać inny AttributeErrorsniż ten, którego się spodziewasz, co może prowadzić do dezorientującego zachowania.

  6. Dostęp do atrybutu jest wolniejszy niż dostęp do zmiennej lokalnej (zwłaszcza jeśli nie jest to zwykły atrybut instancji). (Chociaż, szczerze mówiąc, mikro-optymalizacja w Pythonie jest często głupim zadaniem.)

Jedną rzeczą, na którą należy uważać, jest to, że jeśli obchodzi Cię przypadek, w którym obj.attributejest ustawiona na Brak, musisz użyć innej wartości wartowniczej.

bilard
źródło
1
+1 - To jest w lidze z dict.get ('my_key', 'default_value') i powinno być szerzej znane
1
Doskonały do ​​typowych przypadków użycia, w których chcesz sprawdzić istnienie i użyć atrybutu z wartością domyślną.
dsalaj
18

Prawie zawsze używam hasattr: to właściwy wybór w większości przypadków.

Problematyczny jest przypadek, gdy klasa nadrzędna __getattr__: hasattrbędzie złapać wszystkie wyjątki zamiast łapania prostu AttributeErrorjak można się spodziewać. Innymi słowy, poniższy kod zostanie wydrukowany, b: Falsechociaż lepiej byłoby zobaczyć ValueErrorwyjątek:

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')

W ten sposób zniknął ważny błąd. Zostało to naprawione w Pythonie 3.2 ( problem 9666 ), gdzie hasattrteraz występuje tylko AttributeError.

Łatwym obejściem jest napisanie takiej funkcji narzędzia:

_notset = object()

def safehasattr(thing, attr):
    return getattr(thing, attr, _notset) is not _notset

To pozwoli na getattrczynienia z sytuacją i może wtedy podnieść odpowiedni wyjątek.

Martin Geisler
źródło
2
Zostało to również nieco poprawione w Pythonie2.6, więc hasattrprzynajmniej nie złapie KeyboardInterruptitp.
poolie
Lub zamiast safehasattrpo prostu getattrkopiować wartość do zmiennej lokalnej, jeśli zamierzasz jej użyć, czym prawie zawsze jesteś.
bilard
@poolie To miłe, nie wiedziałem, że hasattrzostało to ulepszone w ten sposób.
Martin Geisler
Tak, to jest dobre. Nie wiedziałem o tym do dzisiaj, kiedy miałem komuś powiedzieć, żeby tego unikał hasattr, i poszedłem sprawdzić. Mieliśmy kilka zabawnych błędów bzr, w których hasattr właśnie połknął ^ C.
bilard
Napotkano problem podczas aktualizacji wersji 2.7 do 3.6. Ta odpowiedź pomoże mi zrozumieć i rozwiązać problem.
Kamesh Jungi
13

Powiedziałbym, że to zależy od tego, czy funkcja może przyjmować obiekty bez atrybutu w fazie projektowania , na przykład, jeśli masz dwie dzwoniących do funkcji, która zapewnia obiektu z atrybutem, a drugi dostarczenie obiektu bez niego.

Jeśli jedyny przypadek, w którym otrzymasz obiekt bez atrybutu, jest spowodowany jakimś błędem, polecam użycie mechanizmu wyjątków, nawet jeśli może być wolniejszy, ponieważ uważam, że jest to bardziej przejrzysty projekt.

Konkluzja: myślę, że jest to raczej kwestia projektu i czytelności niż kwestia wydajności.

Roee Adler
źródło
1
+1 za podkreślanie, dlaczego „spróbuj” ma znaczenie dla osób, które czytają kod. :)
Eric O Lebigot
5

Jeśli brak atrybutu nie jest stanem błędu, wariant obsługi wyjątków ma problem: przechwytuje również błędy AttributeErrors, które mogą pojawić się wewnętrznie podczas uzyskiwania dostępu do obj.attribute (na przykład, ponieważ atrybut jest właściwością, więc dostęp do niej wywołuje kod).

UncleZeiv
źródło
jest to poważny problem, który moim zdaniem został w dużej mierze zignorowany.
Rick wspiera Monikę
5

Temat ten został omówiony w wykładzie EuroPython 2016 Writing szybciej Python przez Sebastiana Witowskiego. Oto reprodukcja jego slajdu z podsumowaniem wyników. Używa również wyglądu terminologii, zanim przejdziesz do tej dyskusji, o czym warto tutaj wspomnieć, aby oznaczyć to słowo kluczowe.

Jeśli rzeczywiście brakuje atrybutu, błaganie o przebaczenie będzie wolniejsze niż proszenie o pozwolenie. Zasadniczo możesz skorzystać z opcji prośby o pozwolenie, jeśli wiesz, że jest bardzo prawdopodobne, że atrybut zostanie pominięty lub wystąpią inne problemy, które możesz przewidzieć. W przeciwnym razie, jeśli spodziewasz się, że kod będzie w większości przypadków czytelny

3 POZWOLENIA CZY PRZEBACZENIE?

# 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
źródło
4

Jeśli testujesz tylko jeden atrybut, powiedziałbym, że użyj hasattr. Jednakże, jeśli robisz kilka dostępów do atrybutów, które mogą istnieć lub nie, użycie trybloku może zaoszczędzić trochę pisania.

n8gray
źródło
3

Proponuję opcję 2. Opcja 1 ma stan wyścigu, jeśli jakiś inny wątek dodaje lub usuwa atrybut.

Python ma również idiom , że EAFP („łatwiej prosić o przebaczenie niż pozwolenie”) jest lepsze niż LBYL („patrz, zanim skoczysz”).

Douglas Leeder
źródło
2

Z praktycznego punktu widzenia, w większości języków użycie warunku zawsze będzie znacznie szybsze niż obsługa wyjątku.

Jeśli chcesz obsłużyć przypadek atrybutu, który nie istnieje gdzieś poza bieżącą funkcją, wyjątek jest lepszym sposobem. Wskaźnikiem, że możesz chcieć użyć wyjątku zamiast warunku, jest to, że warunek po prostu ustawia flagę i przerywa bieżącą operację, a coś w innym miejscu sprawdza tę flagę i na jej podstawie podejmuje działania.

To powiedziawszy, jak wskazuje Rax Olgud, komunikacja z innymi jest jednym z ważnych atrybutów kodu i to, co chcesz powiedzieć, mówiąc „to jest wyjątkowa sytuacja”, a nie „to jest coś, czego oczekuję”, może być ważniejsze .

cjs
źródło
+1 za naleganie na fakt, że „próba” może być interpretowana jako „to jest wyjątkowa sytuacja” w porównaniu z testem warunkowym. :)
Eric O Lebigot
0

Pierwszy.

Krótszy znaczy lepszy. Wyjątki powinny być wyjątkowe.

Nieznany
źródło
5
Wyjątki są bardzo powszechne w Pythonie - jeden znajduje się na końcu każdej forinstrukcji i też go hasattrużywa. Jednak „krótszy jest lepszy” (i „prostszy jest lepszy”!) NALEŻY stosować, więc prostszy, krótszy i bardziej szczegółowy hasattr jest rzeczywiście lepszy.
Alex Martelli
@Alex tylko dlatego, że parser Pythona przekształca te instrukcje w 1, nie oznacza, że ​​jest bardzo powszechny. Jest powód, dla którego zrobili ten cukier syntaktyczny: więc nie utkniesz w okrucieństwie wpisywania try z wyjątkiem bloku.
Nieznany
Jeśli wyjątek jest wyjątkowy, wówczas „wyraźne jest lepsze”, a druga opcja oryginalnego plakatu jest lepsza, powiedziałbym…
Eric O Lebigot
0

Przynajmniej wtedy, gdy zależy to tylko od tego, co dzieje się w programie, pomijając ludzką część czytelności itp. (Co jest właściwie przez większość czasu ważniejsze niż wydajność (przynajmniej w tym przypadku - z tym zakresem wydajności), jak zauważyli Roee Adler i inni).

Jednak patrząc na to z tej perspektywy, staje się kwestią wyboru między

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

i

try: obj.attr
except: ...

ponieważ hasattrużywa tylko pierwszego przypadku do określenia wyniku. Do przemyślenia ;-)

Lewita
źródło