Unterschied zwischen __getattr__ und __getattribute__

Antworten:

496

Ein wesentlicher Unterschied zwischen __getattr__und __getattribute__besteht darin, dass __getattr__nur aufgerufen wird, wenn das Attribut nicht auf die übliche Weise gefunden wurde. Es ist gut für die Implementierung eines Fallbacks für fehlende Attribute und wahrscheinlich eines von zwei, die Sie möchten.

__getattribute__wird aufgerufen, bevor die tatsächlichen Attribute des Objekts angezeigt werden, und kann daher schwierig zu implementieren sein. Sie können sehr leicht in unendliche Rekursionen geraten.

Klassen neuen Stils leiten sich von Klassen objectalten Stils ab, die in Python 2.x ohne explizite Basisklasse vorliegen. Bei der Wahl zwischen __getattr__und ist jedoch die Unterscheidung zwischen Klassen im alten und im neuen Stil nicht wichtig __getattribute__.

Sie wollen mit ziemlicher Sicherheit __getattr__.

Ned Batchelder
quelle
6
Kann ich beide in derselben Klasse implementieren? Wenn ich kann, was ist es für die Implementierung der beiden?
Alcott
13
@Alcott: Sie können beide implementieren, aber ich bin mir nicht sicher, warum Sie das tun würden. __getattribute__wird für jeden Zugriff aufgerufen und __getattr__wird für die Zeiten aufgerufen, __getattribute__die eine ausgelöst haben AttributeError. Warum nicht einfach alles in einem behalten?
Ned Batchelder
10
@NedBatchelder, wenn Sie Aufrufe an vorhandene Methoden (bedingt) überschreiben möchten, möchten Sie verwenden __getattribute__.
Jace Browning
28
"Um eine unendliche Rekursion in dieser Methode zu vermeiden, sollte ihre Implementierung immer die Basisklassenmethode mit demselben Namen aufrufen, um auf alle benötigten Attribute zuzugreifen, z. B. Objekt .__ getattribute __ (self, name)."
kmonsoor
1
@kmonsoor. Das ist ein guter Grund, beides zu implementieren. objec.__getattribute__ruft myclass.__getattr__unter den richtigen Umständen auf.
Mad Physicist
138

Sehen wir uns einige einfache Beispiele für beide __getattr__und __getattribute__magische Methoden an.

__getattr__

Python ruft die __getattr__Methode auf, wenn Sie ein Attribut anfordern, das noch nicht definiert wurde. Im folgenden Beispiel hat meine Klasse Count keine __getattr__Methode. Wenn ich jetzt versuche, auf beide obj1.myminund obj1.mymaxAttribute zuzugreifen, funktioniert alles einwandfrei. Aber wenn ich versuche, auf ein obj1.mycurrentAttribut zuzugreifen , gibt mir PythonAttributeError: 'Count' object has no attribute 'mycurrent'

class Count():
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent)  --> AttributeError: 'Count' object has no attribute 'mycurrent'

Nun meine Klasse Count hat __getattr__Methode. Wenn ich jetzt versuche, auf obj1.mycurrentAttribut zuzugreifen , gibt Python mir alles zurück, was ich in meiner __getattr__Methode implementiert habe . In meinem Beispiel erstellt Python dieses Attribut, wenn ich versuche, ein nicht vorhandenes Attribut aufzurufen, und setzt es auf den ganzzahligen Wert 0.

class Count:
    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax    

    def __getattr__(self, item):
        self.__dict__[item]=0
        return 0

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.mycurrent1)

__getattribute__

Nun sehen wir uns die __getattribute__Methode an. Wenn Sie eine __getattribute__Methode in Ihrer Klasse haben, ruft Python diese Methode für jedes Attribut auf, unabhängig davon, ob es vorhanden ist oder nicht. Warum brauchen wir also eine __getattribute__Methode? Ein guter Grund ist, dass Sie den Zugriff auf Attribute verhindern und sie sicherer machen können, wie im folgenden Beispiel gezeigt.

Immer wenn jemand versucht, auf meine Attribute zuzugreifen, die mit der Teilzeichenfolge 'cur' Python beginnen, wird eine AttributeErrorAusnahme ausgelöst . Andernfalls wird dieses Attribut zurückgegeben.

class Count:

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item) 
        # or you can use ---return super().__getattribute__(item)

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)

Wichtig: Um eine unendliche Rekursion in der __getattribute__Methode zu vermeiden , sollte ihre Implementierung immer die Basisklassenmethode mit demselben Namen aufrufen, um auf alle benötigten Attribute zuzugreifen. Zum Beispiel: object.__getattribute__(self, name)oder super().__getattribute__(item)und nichtself.__dict__[item]

WICHTIG

Wenn Ihre Klasse sowohl die magischen Methoden getattr als auch getattribute enthält , __getattribute__wird sie zuerst aufgerufen. Aber wenn __getattribute__Raises AttributeErrorAusnahme dann wird die Ausnahme ignoriert und __getattr__Methode wird aufgerufen. Siehe folgendes Beispiel:

class Count(object):

    def __init__(self,mymin,mymax):
        self.mymin=mymin
        self.mymax=mymax
        self.current=None

    def __getattr__(self, item):
            self.__dict__[item]=0
            return 0

    def __getattribute__(self, item):
        if item.startswith('cur'):
            raise AttributeError
        return object.__getattribute__(self,item)
        # or you can use ---return super().__getattribute__(item)
        # note this class subclass object

obj1 = Count(1,10)
print(obj1.mymin)
print(obj1.mymax)
print(obj1.current)
N Randhawa
quelle
1
Ich bin mir nicht sicher, was genau ein Anwendungsfall für das Überschreiben wäre, __getattribute__aber das ist es sicherlich nicht. Denn gemäß Ihrem Beispiel lösen Sie nur __getattribute__eine AttributeErrorAusnahme aus, wenn das Attribut im __dict__Objekt nicht vorhanden ist. Dies ist jedoch nicht unbedingt erforderlich, da dies die Standardimplementierung von ist __getattribute__und infact __getattr__genau das ist, was Sie als Fallback-Mechanismus benötigen.
Rohit
@Rohit currentauf Instanzen definiert Count(siehe __init__), die Erhöhung so einfach , AttributeErrorwenn das Attribut ist nicht , ist nicht ganz das, was passiert ist - es aufschiebt __getattr__für alle Namen ‚cur‘ beginnen, einschließlich current, aber auch curious, curly...
joelb
16

Dies ist nur ein Beispiel, das auf der Erklärung von Ned Batchelder basiert .

__getattr__ Beispiel:

class Foo(object):
    def __getattr__(self, attr):
        print "looking up", attr
        value = 42
        self.__dict__[attr] = value
        return value

f = Foo()
print f.x 
#output >>> looking up x 42

f.x = 3
print f.x 
#output >>> 3

print ('__getattr__ sets a default value if undefeined OR __getattr__ to define how to handle attributes that are not found')

Und wenn das gleiche Beispiel mit verwendet wird, erhalten __getattribute__Sie >>>RuntimeError: maximum recursion depth exceeded while calling a Python object

Simon K Bhatta4ya
quelle
9
Eigentlich ist das schrecklich. In realen __getattr__()Implementierungen wird nur eine begrenzte Anzahl gültiger Attributnamen akzeptiert, indem AttributeErrorungültige Attributnamen ausgelöst werden, wodurch subtile und schwer zu debuggende Probleme vermieden werden . In diesem Beispiel werden alle Attributnamen unbedingt als gültig akzeptiert - ein bizarrer (und offen gesagt fehleranfälliger) Missbrauch von __getattr__(). Wenn Sie wie in diesem Beispiel die vollständige Kontrolle über die Attributerstellung wünschen, möchten Sie __getattribute__()stattdessen.
Cecil Curry
3
@CecilCurry: Bei allen Problemen, auf die Sie verlinkt haben, wird implizit None anstelle eines Werts zurückgegeben, was bei dieser Antwort nicht der Fall ist. Was ist falsch daran, alle Attributnamen zu akzeptieren? Es ist das gleiche wie ein defaultdict.
Nick Matteo
2
Das Problem ist, dass __getattr__es vor der Suche nach Superklassen aufgerufen wird . Dies ist für eine direkte Unterklasse von in Ordnung object, da die einzigen Methoden, die Sie wirklich interessieren, magische Methoden sind, die die Instanz ohnehin ignorieren. Bei einer komplexeren Vererbungsstruktur entfernen Sie jedoch vollständig die Möglichkeit, etwas vom übergeordneten Element zu erben.
Mad Physicist
1
@ Simon K Bhatta4ya, Ihre letzte Druckaussage ist ein Kommentar. Recht? Es ist eine lange Schlange und mühsam zu lesen (man muss viel auf der rechten Seite scrollen). Was ist mit der Zeile nach dem Codeabschnitt? Oder wenn Sie es in den Codeabschnitt einfügen möchten, ist es meiner Meinung nach besser, es in zwei verschiedene Zeilen zu unterteilen.
Md. Abu Nafee Ibna Zahid
14

Klassen objectneuen Stils erben von oder von einer anderen neuen Stilklasse:

class SomeObject(object):
    pass

class SubObject(SomeObject):
    pass

Old-Style-Klassen nicht:

class SomeObject:
    pass

Dies gilt nur für Python 2 - in Python 3 werden mit allen oben genannten Methoden Klassen neuen Stils erstellt.

Siehe 9. Klassen (Python-Tutorial), NewClassVsClassicClass und Was ist der Unterschied zwischen alten und neuen Stilklassen in Python? für Details.

Sam Dolan
quelle
6

Klassen neuen Stils sind solche, die "Objekt" (direkt oder indirekt) unterordnen. Sie haben __new__zusätzlich zu __init__und etwas rationaleres Verhalten auf niedriger Ebene eine Klassenmethode .

Normalerweise möchten Sie überschreiben __getattr__(wenn Sie dies auch überschreiben), andernfalls fällt es Ihnen schwer, die Syntax "self.foo" in Ihren Methoden zu unterstützen.

Zusätzliche Informationen: http://www.devx.com/opensource/Article/31482/0/page/4

Herr Fooz
quelle
2

Beim Lesen von Beazley & Jones PCB bin ich auf einen expliziten und praktischen Anwendungsfall gestoßen __getattr__, der bei der Beantwortung des "Wann" -Teils der OP-Frage hilft. Von dem Buch:

"Die __getattr__()Methode ähnelt einem Sammelbegriff für die Attributsuche. Diese Methode wird aufgerufen, wenn Code versucht, auf ein nicht vorhandenes Attribut zuzugreifen." Wir wissen dies aus den obigen Antworten, aber in PCB-Rezept 8.15 wird diese Funktionalität verwendet, um das Delegationsdesignmuster zu implementieren . Wenn Objekt A ein Attribut Objekt B hat, das viele Methoden implementiert, an die Objekt A delegieren möchte, anstatt alle Methoden von Objekt B in Objekt A neu zu definieren, nur um die Methoden von Objekt B aufzurufen, definieren Sie eine __getattr__()Methode wie folgt:

def __getattr__(self, name):
    return getattr(self._b, name)

Dabei ist _b der Name des Attributs von Objekt A, das ein Objekt B ist. Wenn eine für Objekt B definierte Methode für Objekt A aufgerufen wird, wird die __getattr__Methode am Ende der Suchkette aufgerufen. Dies würde auch den Code sauberer machen, da Sie keine Liste von Methoden definiert haben, die nur zum Delegieren an ein anderes Objekt definiert sind.

einschläfernd312
quelle