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:
hasattr
wewnętrznie i szybko wykonuje to samo zadanie co try/except
blok: jest to bardzo specyficzne, zoptymalizowane narzędzie do jednego zadania i dlatego powinno być preferowane, gdy ma to zastosowanie, zamiast bardzo ogólnego przeznaczenia.
hasattr
spowoduje to przechwycenie wszystkich wyjątków w Pythonie 2.x. Zobacz moją odpowiedź na przykład i trywialne obejście.
try
może przekazać, że operacja powinna działać. Chociaż try
zamiar 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
try
jest około dwa razy szybszy niż hasattr()
. Jeśli tak nie try
jest , 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 try
prawie 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:
getattr
nie ma złego zachowania polegającego na połykaniu wyjątków, wskazanego przez Martina Geisera - w starych Pythonach hasattr
nawet 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/finally
i często krótszy niż hasattr
.
Szeroki except AttributeError
blok może złapać inny AttributeErrors
niż 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.attribute
jest 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__
: hasattr
będzie złapać wszystkie wyjątki zamiast łapania prostu AttributeError
jak można się spodziewać. Innymi słowy, poniższy kod zostanie wydrukowany, b: False
chociaż lepiej byłoby zobaczyć ValueError
wyją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 hasattr
teraz 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 getattr
czynienia z sytuacją i może wtedy podnieść odpowiedni wyjątek.
hasattr
przynajmniej nie złapie KeyboardInterrupt
itp.
safehasattr
po prostu getattr
kopiować wartość do zmiennej lokalnej, jeśli zamierzasz jej użyć, czym prawie zawsze jesteś.
hasattr
został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.
for
instrukcji i też go hasattr
uż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ż hasattr
używa tylko pierwszego przypadku do określenia wyniku. Do przemyślenia ;-)