if hasattr(obj, 'attribute'):
# do somthing
vs
try:
# access obj.attribute
except AttributeError, e:
# deal with AttributeError
Quale dovrebbe essere preferito e perché?
Risposte:
hasattr
internamente e rapidamente esegue lo stesso compito del try/except
blocco: è uno strumento molto specifico, ottimizzato, one-task e quindi dovrebbe essere preferito, quando applicabile, all'alternativa molto generica.
hasattr
si cattura tutte le eccezioni in Python 2.x. Vedi la mia risposta per un esempio e la banale soluzione alternativa.
try
può trasmettere che l'operazione dovrebbe funzionare. Sebbene try
l'intento non sia sempre tale, è comune, quindi potrebbe essere considerato più leggibile.
Eventuali panchine che illustrano la differenza di prestazioni?
timeit è il tuo amico
$ 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
è circa il doppio più veloce di hasattr()
. In caso contrario, try
è circa 1,5 volte più lento di hasattr()
(ed entrambi sono sostanzialmente più lenti rispetto a se l'attributo esiste). Ciò è probabilmente dovuto al fatto che, sul percorso felice, try
non fa quasi nulla (Python sta già pagando il sovraccarico delle eccezioni indipendentemente dal fatto che le usi), ma hasattr()
richiede una ricerca del nome e una chiamata di funzione. Sul percorso infelice, entrambi devono gestire alcune eccezioni e a goto
, ma hasattr()
lo fanno in C invece che in bytecode Python.
C'è una terza, e spesso migliore, alternativa:
attr = getattr(obj, 'attribute', None)
if attr is not None:
print attr
Vantaggi:
getattr
non ha il cattivo comportamento di deglutizione delle eccezioni indicato da Martin Geiser - nei vecchi Pythons, hasattr
ingoierà persino un KeyboardInterrupt
.
La ragione normale per cui controlli se l'oggetto ha un attributo è che puoi usare l'attributo, e questo naturalmente porta ad esso.
L'attributo viene letto in modo atomico ed è al sicuro da altri thread che cambiano l'oggetto. (Tuttavia, se questa è una delle principali preoccupazioni, potresti prendere in considerazione la possibilità di bloccare l'oggetto prima di accedervi.)
È più corto di try/finally
e spesso più corto di hasattr
.
Un except AttributeError
blocco ampio può catturare diversi AttributeErrors
da quello che ti aspetti, il che può portare a comportamenti confusi.
L'accesso a un attributo è più lento rispetto all'accesso a una variabile locale (specialmente se non è un semplice attributo di istanza). (Anche se, ad essere onesti, la microottimizzazione in Python è spesso una commissione da pazzi.)
Una cosa a cui fare attenzione è che se ti interessa il caso in cui obj.attribute
è impostato su Nessuno, dovrai utilizzare un valore sentinella diverso.
Lo uso quasi sempre hasattr
: è la scelta corretta per la maggior parte dei casi.
Il caso problematico è quando una classe ha la precedenza __getattr__
: hasattr
si cattura tutte le eccezioni invece di catturare proprio AttributeError
come ci si aspetta. In altre parole, il codice seguente verrà stampato b: False
anche se sarebbe più appropriato vedere ValueError
un'eccezione:
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')
L'importante errore è così scomparso. Questo problema è stato risolto in Python 3.2 ( problema 9666 ) dove hasattr
ora cattura soloAttributeError
.
Una soluzione semplice è scrivere una funzione di utilità come questa:
_notset = object()
def safehasattr(thing, attr):
return getattr(thing, attr, _notset) is not _notset
Questo getattr
affrontiamo la situazione e può quindi sollevare l'eccezione appropriata.
hasattr
almeno non catturi KeyboardInterrupt
ecc.
safehasattr
, usa semplicemente getattr
per copiare il valore in una variabile locale se lo usi, cosa che quasi sempre sei.
hasattr
fosse stato migliorato così.
hasattr
, e sono andato a controllare. Abbiamo avuto alcuni bug bzr divertenti in cui hasattr ha appena ingoiato ^ C.
Direi che dipende dal fatto che la tua funzione possa accettare oggetti senza l'attributo in base alla progettazione , ad esempio se hai due chiamanti alla funzione, uno che fornisce un oggetto con l'attributo e l'altro che fornisce un oggetto senza di esso.
Se l'unico caso in cui otterrai un oggetto senza l'attributo è dovuto a un errore, consiglierei di utilizzare il meccanismo delle eccezioni anche se potrebbe essere più lento, perché credo che sia un design più pulito.
In conclusione: penso che sia un problema di design e leggibilità piuttosto che un problema di efficienza.
Se non avere l'attributo non è una condizione di errore, la variante di gestione delle eccezioni ha un problema: catturerebbe anche AttributeErrors che potrebbero venire internamente quando si accede a obj.attribute (ad esempio perché l'attributo è una proprietà quindi accedendo chiama del codice).
Questo argomento è stato trattato nel discorso EuroPython 2016 Scrivere più velocemente Python di Sebastian Witowski. Ecco una riproduzione della sua diapositiva con il riepilogo delle prestazioni. Usa anche l' aspetto della terminologia prima di saltare in questa discussione, vale la pena menzionarlo qui per taggare quella parola chiave.
Se l'attributo è effettivamente mancante, chiedere perdono sarà più lento che chiedere i permessi. Quindi, come regola pratica, puoi utilizzare la modalità di richiesta di autorizzazione se sai che è molto probabile che l'attributo manchi o altri problemi che puoi prevedere. Altrimenti, se ti aspetti che il codice risulti nella maggior parte delle volte leggibile
# 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
Suggerirei l'opzione 2. L'opzione 1 ha una condizione di competizione se qualche altro thread aggiunge o rimuove l'attributo.
Anche Python ha un Idiom , che EAFP ( 'più facile chiedere perdono che il permesso') è meglio di LBYL ( 'guardare prima di saltare').
Da un punto di vista pratico, nella maggior parte delle lingue l'uso di un condizionale sarà sempre notevolmente più veloce che gestire un'eccezione.
Se vuoi gestire il caso di un attributo che non esiste da qualche parte al di fuori della funzione corrente, l'eccezione è il modo migliore per procedere. Un indicatore del fatto che potresti voler utilizzare un'eccezione invece di un condizionale è che il condizionale imposta semplicemente un flag e interrompe l'operazione corrente, e qualcos'altro controlla questo flag e agisce in base a quello.
Detto questo, come sottolinea Rax Olgud, la comunicazione con gli altri è un attributo importante del codice, e ciò che vuoi dire dicendo "questa è una situazione eccezionale" piuttosto che "questo è qualcosa che mi aspetto che accada" potrebbe essere più importante .
Il primo.
Più corto è meglio. Le eccezioni dovrebbero essere eccezionali.
for
istruzione e ne hasattr
usa anche una. Tuttavia, "più corto è meglio" (e "più semplice è meglio"!) DO si applica, quindi è preferibile hasattr più semplice, più breve e più specifico.
Almeno quando dipende solo da cosa sta succedendo nel programma, tralasciando la parte umana della leggibilità, ecc. (Che in realtà è il più delle volte più importante delle prestazioni (almeno in questo caso - con quella durata delle prestazioni), come hanno sottolineato Roee Adler e altri).
Tuttavia, guardandolo da quella prospettiva, diventa quindi una questione di scelta tra
try: getattr(obj, attr)
except: ...
e
try: obj.attr
except: ...
poiché hasattr
utilizza solo il primo caso per determinare il risultato. Cibo per la mente ;-)