hasattr () vs try-Except-Block, um mit nicht vorhandenen Attributen umzugehen

Antworten:

81

hasattrführt intern und schnell dieselbe Aufgabe wie der try/exceptBlock aus: Es ist ein sehr spezifisches, optimiertes Tool für eine Aufgabe und sollte daher gegebenenfalls der sehr universellen Alternative vorgezogen werden.

Alex Martelli
quelle
8
Außer Sie benötigen immer noch den Try / Catch-Block, um die Rennbedingungen zu bewältigen (wenn Sie Threads verwenden).
Douglas Leeder
1
Oder der Sonderfall, auf den ich gerade gestoßen bin: Ein Django OneToOneField ohne Wert: hasattr (obj, Feldname) gibt False zurück, aber es gibt ein Attribut mit Feldname: Es wird nur ein DoesNotExist-Fehler ausgelöst.
Matthew Schinckel
3
Beachten Sie, dass hasattrwerden alle Ausnahmen fangen in Python 2.x. In meiner Antwort finden Sie ein Beispiel und die einfache Problemumgehung.
Martin Geisler
4
Ein interessanter Kommentar : trykann vermitteln, dass die Operation funktionieren sollte . Obwohl trydie Absicht nicht immer so ist, ist sie üblich, so dass sie als besser lesbar angesehen werden kann.
Ioannis Filippidis
87

Gibt es Bänke, die Leistungsunterschiede veranschaulichen?

Timeit, es ist dein Freund

$ 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
quelle
16
+1 für die Bereitstellung interessanter, greifbarer Zahlen. Tatsächlich ist der "Versuch" effizient, wenn er den allgemeinen Fall enthält (dh wenn eine Python-Ausnahme wirklich außergewöhnlich ist).
Eric O Lebigot
Ich bin mir nicht sicher, wie ich diese Ergebnisse interpretieren soll. Was ist hier schneller und um wie viel?
Stevoisiak
2
@ StevenM.Vascellaro: Wenn das Attribut existiert, tryist es ungefähr doppelt so schnell wie hasattr(). Wenn dies nicht der Fall ist, tryist es ungefähr 1,5x langsamer als hasattr()(und beide sind wesentlich langsamer als wenn das Attribut vorhanden ist). Dies liegt wahrscheinlich daran, dass auf dem glücklichen Pfad trykaum etwas unternommen wird (Python zahlt bereits für den Overhead von Ausnahmen, unabhängig davon, ob Sie sie verwenden), hasattr()erfordert jedoch eine Namenssuche und einen Funktionsaufruf. Auf dem unglücklichen Pfad müssen beide eine Ausnahmebehandlung und a ausführen goto, hasattr()dies jedoch in C und nicht in Python-Bytecode.
Kevin
24

Es gibt eine dritte und oft bessere Alternative:

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

Vorteile:

  1. getattrhat nicht das schlechte Ausnahme-Schluckverhalten, auf das Martin Geiser hingewiesen hat - in alten Pythons hasattrwird sogar ein schlucken KeyboardInterrupt.

  2. Der normale Grund, warum Sie prüfen, ob das Objekt ein Attribut hat, besteht darin, dass Sie das Attribut verwenden können, und dies führt natürlich dazu.

  3. Das Attribut wird atomar abgelesen und ist vor anderen Threads geschützt, die das Objekt ändern. (Wenn dies jedoch ein wichtiges Anliegen ist, sollten Sie das Objekt vor dem Zugriff sperren.)

  4. Es ist kürzer als try/finallyund oft kürzer als hasattr.

  5. Ein breiter except AttributeErrorBlock kann andere AttributeErrorsals den von Ihnen erwarteten fangen , was zu verwirrendem Verhalten führen kann.

  6. Der Zugriff auf ein Attribut ist langsamer als der Zugriff auf eine lokale Variable (insbesondere, wenn es sich nicht um ein einfaches Instanzattribut handelt). (Um ehrlich zu sein, ist die Mikrooptimierung in Python oft ein Kinderspiel.)

Wenn Sie sich für den Fall interessieren obj.attribute, in dem Keine festgelegt ist, müssen Sie einen anderen Sentinel-Wert verwenden.

Poolie
quelle
1
+1 - Dies steht im Einklang mit dict.get ('my_key', 'default_value') und sollte besser bekannt sein über
1
Ideal für den allgemeinen Anwendungsfall, bei dem Sie die Existenz überprüfen und das Attribut mit dem Standardwert verwenden möchten.
Dsalaj
18

Ich benutze fast immer hasattr: Es ist in den meisten Fällen die richtige Wahl.

Der problematische Fall ist , wenn eine Klasse überschreibt __getattr__: hasattrwerden alle Ausnahmen fangen , anstatt nur zu fangen , AttributeErrorwie Sie es erwarten. Mit anderen Worten, der folgende Code wird gedruckt b: False, obwohl es angemessener wäre, eine ValueErrorAusnahme zu sehen :

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

Der wichtige Fehler ist damit verschwunden. Dies wurde in Python 3.2 ( Problem 9666 ) behoben, wo hasattrjetzt nur noch Fänge auftreten AttributeError.

Eine einfache Problemumgehung besteht darin, eine Dienstprogrammfunktion wie die folgende zu schreiben:

_notset = object()

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

Lassen Sie uns getattrdie Situation behandeln und dann die entsprechende Ausnahme auslösen.

Martin Geisler
quelle
2
Dies wurde auch ein wenig in Python2.6 verbessert, so dass hasattrzumindest nicht fangen KeyboardInterruptusw.
Poolie
Oder kopieren Sie den Wert safehasattreinfach getattrin eine lokale Variable, wenn Sie ihn verwenden möchten, was Sie fast immer sind.
Poolie
@poolie Das ist schön, ich wusste nicht, dass hasattrdas so verbessert wurde.
Martin Geisler
Ja es ist gut. Das wusste ich auch nicht, bis ich heute jemandem sagen wollte, er solle es vermeiden hasattr, und ging, um nachzusehen. Wir hatten einige lustige BZR-Bugs, bei denen Hasattr gerade geschluckt hat ^ C.
Poolie
Beim Upgrade von 2.7 auf 3.6 trat ein Problem auf. Diese Antwort hilft mir, das Problem zu verstehen und zu lösen.
Kamesh Jungi
13

Ich würde sagen, es hängt davon ab, ob Ihre Funktion Objekte ohne das Attribut beabsichtigt , z. B. wenn Sie zwei Aufrufer für die Funktion haben, von denen einer ein Objekt mit dem Attribut und der andere ein Objekt ohne das Attribut bereitstellt.

Wenn der einzige Fall, in dem Sie ein Objekt ohne das Attribut erhalten, auf einen Fehler zurückzuführen ist, würde ich die Verwendung des Ausnahmemechanismus empfehlen, auch wenn dieser möglicherweise langsamer ist, da ich glaube, dass es sich um ein saubereres Design handelt.

Fazit: Ich denke, es ist eher ein Design- und Lesbarkeitsproblem als ein Effizienzproblem.

Roee Adler
quelle
1
+1 für das Beharren darauf, warum "versuchen" eine Bedeutung für Leute hat, die den Code lesen. :)
Eric O Lebigot
5

Wenn das Fehlen des Attributs keine Fehlerbedingung ist, hat die Ausnahmebehandlungsvariante ein Problem: Sie fängt auch AttributeErrors ab, die möglicherweise intern beim Zugriff auf obj.attribute auftreten (z. B. weil attribute eine Eigenschaft ist, sodass beim Zugriff darauf Code aufgerufen wird).

OnkelZeiv
quelle
Dies ist ein großes Problem, das meiner Meinung nach weitgehend ignoriert wurde.
Rick unterstützt Monica
4

Wenn es nur ein Attribut ist, das Sie testen, würde ich verwenden verwenden hasattr. Wenn Sie jedoch mehrere Zugriffe auf Attribute ausführen, die möglicherweise vorhanden sind oder nicht, tryerspart Ihnen die Verwendung eines Blocks möglicherweise einige Eingaben.

n8gray
quelle
3

Ich würde Option 2 vorschlagen. Option 1 hat eine Race-Bedingung, wenn ein anderer Thread das Attribut hinzufügt oder entfernt.

Auch Python hat eine Redewendung , dass EAFP ("leichter um Vergebung als um Erlaubnis zu bitten") besser ist als LBYL ("schau bevor du springst").

Douglas Leeder
quelle
3

Dieses Thema wurde im EuroPython 2016-Vortrag „ Schnelleres Python schreiben“ von Sebastian Witowski behandelt. Hier ist eine Reproduktion seiner Folie mit der Leistungsübersicht. Er verwendet auch den Terminologie- Look, bevor Sie in diese Diskussion einsteigen. Erwähnenswert ist hier, um dieses Schlüsselwort zu kennzeichnen.

Wenn das Attribut tatsächlich fehlt, ist das Bitten um Vergebung langsamer als das Bitten um Berechtigungen. Als Faustregel können Sie die Frage nach der Erlaubnis verwenden, wenn Sie wissen, dass das Attribut sehr wahrscheinlich fehlt oder andere Probleme, die Sie vorhersagen können. Andernfalls führt Code, wenn Sie erwarten, meistens zu lesbarem Code

3 ERLAUBNISSE ODER VERGEBUNG?

# 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
quelle
2

Aus praktischer Sicht ist die Verwendung einer Bedingung in den meisten Sprachen immer wesentlich schneller als die Behandlung einer Ausnahme.

Wenn Sie den Fall eines Attributs behandeln möchten, das nicht außerhalb der aktuellen Funktion vorhanden ist, ist die Ausnahme der bessere Weg. Ein Indikator dafür, dass Sie möglicherweise eine Ausnahme anstelle einer Bedingung verwenden möchten, ist, dass die Bedingung lediglich ein Flag setzt und die aktuelle Operation abbricht. An anderer Stelle wird dieses Flag überprüft und basierend darauf Maßnahmen ergriffen.

Wie Rax Olgud betont, ist die Kommunikation mit anderen ein wichtiges Attribut des Codes, und was Sie sagen möchten, indem Sie sagen, dass dies eine Ausnahmesituation ist und nicht, dass dies etwas ist, von dem ich erwarte, dass es passiert, ist möglicherweise wichtiger .

cjs
quelle
+1 für das Beharren auf der Tatsache, dass "Versuch" im Vergleich zum bedingten Test als "Dies ist eine Ausnahmesituation" interpretiert werden kann. :)
Eric O Lebigot
0

Der Erste.

Kürzer ist besser. Ausnahmen sollten außergewöhnlich sein.

Unbekannt
quelle
5
Ausnahmen sind in Python sehr häufig - es gibt eine am Ende jeder forAnweisung und hasattrverwendet auch eine. Es gilt jedoch "kürzer ist besser" (und "einfacher ist besser"!). Daher ist das einfachere, kürzere und spezifischere Hasattr in der Tat vorzuziehen.
Alex Martelli
@Alex Nur weil der Python-Parser diese Anweisungen in 1 umwandelt, bedeutet dies nicht, dass sie sehr häufig sind. Es gibt einen Grund, warum sie diesen syntaktischen Zucker hergestellt haben: Sie sind also nicht mit der Kruftigkeit des Tippens des Versuchs außer Block konfrontiert.
Unbekannt
Wenn die Ausnahme außergewöhnlich ist, dann ist "explizit ist besser" und die zweite Option des Originalplakats ist besser, würde ich sagen ...
Eric O Lebigot
0

Zumindest, wenn es nur darum geht, was im Programm vor sich geht, den menschlichen Teil der Lesbarkeit usw. wegzulassen (was eigentlich meistens wichtiger ist als die Leistung (zumindest in diesem Fall - mit dieser Leistungsspanne)). wie Roee Adler und andere betonten).

Wenn man es jedoch aus dieser Perspektive betrachtet, ist es dann eine Frage der Wahl zwischen

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

und

try: obj.attr
except: ...

da verwendet hasattrnur den ersten Fall, um das Ergebnis zu bestimmen. Denkanstöße ;-)

Levit
quelle