hasattr () vs try-eccetto blocco per gestire attributi inesistenti


Risposte:


82

hasattrinternamente e rapidamente esegue lo stesso compito del try/exceptblocco: è uno strumento molto specifico, ottimizzato, one-task e quindi dovrebbe essere preferito, quando applicabile, all'alternativa molto generica.


8
Tranne che hai ancora bisogno del blocco try / catch per gestire le condizioni di competizione (se stai usando thread).
Douglas Leeder

1
Oppure, il caso speciale che ho appena incontrato: un django OneToOneField senza valore: hasattr (obj, field_name) restituisce False, ma c'è un attributo con field_name: solleva solo un errore DoesNotExist.
Matthew Schinckel

3
Si noti che hasattrsi cattura tutte le eccezioni in Python 2.x. Vedi la mia risposta per un esempio e la banale soluzione alternativa.
Martin Geisler

5
Un commento interessante : trypuò trasmettere che l'operazione dovrebbe funzionare. Sebbene tryl'intento non sia sempre tale, è comune, quindi potrebbe essere considerato più leggibile.
Ioannis Filippidis

88

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

16
+1 per fornire numeri interessanti e tangibili. In effetti, il "try" è efficiente quando contiene il caso comune (cioè quando un'eccezione Python è davvero eccezionale).
Eric O Lebigot

Non sono sicuro di come interpretare questi risultati. Quale è più veloce qui e di quanto?
Stevoisiak

2
@ StevenM.Vascellaro: se l'attributo esiste, 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, trynon 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.
Kevin

24

C'è una terza, e spesso migliore, alternativa:

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

Vantaggi:

  1. getattrnon ha il cattivo comportamento di deglutizione delle eccezioni indicato da Martin Geiser - nei vecchi Pythons, hasattringoierà persino un KeyboardInterrupt.

  2. La ragione normale per cui controlli se l'oggetto ha un attributo è che puoi usare l'attributo, e questo naturalmente porta ad esso.

  3. 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.)

  4. È più corto di try/finallye spesso più corto di hasattr.

  5. Un except AttributeErrorblocco ampio può catturare diversi AttributeErrorsda quello che ti aspetti, il che può portare a comportamenti confusi.

  6. 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.


1
+1 - Questo è in sintonia con dict.get ('my_key', 'default_value') e dovrebbe essere più ampiamente conosciuto

1
Ottimo per casi d'uso comuni in cui si desidera verificare l'esistenza e utilizzare l'attributo con il valore predefinito.
dsalaj

18

Lo uso quasi sempre hasattr : è la scelta corretta per la maggior parte dei casi.

Il caso problematico è quando una classe ha la precedenza __getattr__: hasattrsi cattura tutte le eccezioni invece di catturare proprio AttributeErrorcome ci si aspetta. In altre parole, il codice seguente verrà stampato b: Falseanche se sarebbe più appropriato vedere ValueErrorun'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 hasattrora 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 getattraffrontiamo la situazione e può quindi sollevare l'eccezione appropriata.


2
Anche questo è stato migliorato un po ' in Python2.6 in modo che hasattralmeno non catturi KeyboardInterruptecc.
poolie

Oppure, piuttosto che safehasattr, usa semplicemente getattrper copiare il valore in una variabile locale se lo usi, cosa che quasi sempre sei.
poolie

@poolie È carino, non sapevo che hasattrfosse stato migliorato così.
Martin Geisler

Si è buono. Neanche questo lo sapevo fino ad oggi, quando stavo per dire a qualcuno di evitare hasattr, e sono andato a controllare. Abbiamo avuto alcuni bug bzr divertenti in cui hasattr ha appena ingoiato ^ C.
poolie

Si è verificato un problema durante l'aggiornamento da 2.7 a 3.6. Questa risposta mi aiuta a capire e risolvere il problema.
Kamesh Jungi,

13

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.


1
+1 per aver insistito sul perché "provare" ha un significato per le persone che leggono il codice. :)
Eric O Lebigot

5

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 è un grave problema che è stato ampiamente ignorato, a mio parere.
Rick sostiene Monica

5

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

3 PERMESSI O PERDONO?

# 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

Se è solo un attributo che stai testando, direi use hasattr. Tuttavia, se stai effettuando diversi accessi ad attributi che possono o non possono esistere, l'utilizzo di un tryblocco potrebbe farti risparmiare un po 'di digitazione.


3

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


2

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 .


+1 per aver insistito sul fatto che "try" può essere interpretato come "questa è una situazione eccezionale", rispetto al test condizionale. :)
Eric O Lebigot

0

Il primo.

Più corto è meglio. Le eccezioni dovrebbero essere eccezionali.


5
Le eccezioni sono molto comuni in Python: ce n'è una alla fine di ogni foristruzione e ne hasattrusa anche una. Tuttavia, "più corto è meglio" (e "più semplice è meglio"!) DO si applica, quindi è preferibile hasattr più semplice, più breve e più specifico.
Alex Martelli

@Alex solo perché il parser Python trasforma quelle istruzioni per avere 1 non significa che sia molto comune. C'è un motivo per cui hanno creato quello zucchero sintattico: quindi non sei bloccato con la crudezza di digitare la prova tranne il blocco.
Sconosciuto

Se l'eccezione è eccezionale, allora "esplicito è meglio" e la seconda opzione del poster originale è migliore, direi ...
Eric O Lebigot,

0

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é hasattrutilizza solo il primo caso per determinare il risultato. Cibo per la mente ;-)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.