if hasattr(obj, 'attribute'):
# do somthing
vs
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Który powinien być preferowany i dlaczego?
Odpowiedzi:
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.
hasattrspowoduje to przechwycenie wszystkich wyjątków w Pythonie 2.x. Zobacz moją odpowiedź na przykład i trywialne obejście.
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.
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
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.
Istnieje trzecia i często lepsza alternatywa:
attr = getattr(obj, 'attribute', None)
if attr is not None:
print attr
Zalety:
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.
Normalnym powodem, dla którego sprawdzasz, czy obiekt ma atrybut, jest to, że możesz użyć atrybutu, a to naturalnie prowadzi do tego.
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).
Jest krótszy try/finallyi często krótszy niż hasattr.
Szeroki except AttributeErrorblok może złapać inny AttributeErrorsniż ten, którego się spodziewasz, co może prowadzić do dezorientującego zachowania.
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.
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.
hasattrprzynajmniej nie złapie KeyboardInterruptitp.
safehasattrpo prostu getattrkopiować wartość do zmiennej lokalnej, jeśli zamierzasz jej użyć, czym prawie zawsze jesteś.
hasattrzostało to ulepszone w ten sposób.
hasattr, i poszedłem sprawdzić. Mieliśmy kilka zabawnych błędów bzr, w których hasattr właśnie połknął ^ C.
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.
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).
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
# 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
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”).
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 .
Pierwszy.
Krótszy znaczy lepszy. Wyjątki powinny być wyjątkowe.
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.
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 ;-)