PEP 213 – Attributzugriffs-Handler
- Autor:
- Paul Prescod <paul at prescod.net>
- Status:
- Verschoben
- Typ:
- Standards Track
- Erstellt:
- 21. Juli 2000
- Python-Version:
- 2.1
- Post-History:
Einleitung
Es ist in Python-Code und in Erweiterungsmodulen möglich (und sogar relativ üblich), abzufangen, wenn der Client-Code einer Instanz versucht, ein Attribut zu setzen und stattdessen Code auszuführen. Mit anderen Worten, es ist möglich, Benutzern die Verwendung von Attributzuweisungs-/Abruf-/Löschsyntax zu ermöglichen, auch wenn die zugrunde liegende Implementierung einige Berechnungen durchführt, anstatt direkt eine Bindung zu ändern.
Diese PEP beschreibt eine Funktion, die die Implementierung dieser Handler für Python-Instanzen einfacher, effizienter und sicherer macht.
Begründung
Szenario 1
Sie haben eine bereitgestellte Klasse, die mit einem Attribut namens „stdout“ arbeitet. Nach einer Weile denken Sie, es wäre besser, beim Zuweisen zu prüfen, ob stdout wirklich ein Objekt mit einer „write“-Methode ist. Anstatt zu einer setstdout-Methode zu wechseln (die mit bereitgestelltem Code inkompatibel wäre), würden Sie lieber die Zuweisung abfangen und den Typ des Objekts überprüfen.
Szenario 2
Sie möchten so kompatibel wie möglich mit einem Objektmodell sein, das ein Konzept der Attributzuweisung hat. Dies könnte das W3C Document Object Model oder eine bestimmte COM-Schnittstelle (z. B. die PowerPoint-Schnittstelle) sein. In diesem Fall möchten Sie möglicherweise, dass Attribute im Modell als Attribute in der Python-Schnittstelle angezeigt werden, auch wenn die zugrunde liegende Implementierung überhaupt keine Attribute verwendet.
Szenario 3
Ein Benutzer möchte ein Attribut schreibgeschützt machen.
Kurz gesagt, diese Funktion ermöglicht es Programmierern, die Schnittstelle ihres Moduls aus beliebigen Gründen von der zugrunde liegenden Implementierung zu trennen. Auch dies ist keine neue Funktion, sondern lediglich eine neue Syntax für eine bestehende Konvention.
Aktuelle Lösung
Um einige Attribute schreibgeschützt zu machen
class foo:
def __setattr__( self, name, val ):
if name=="readonlyattr":
raise TypeError
elif name=="readonlyattr2":
raise TypeError
...
else:
self.__dict__["name"]=val
Dies hat die folgenden Probleme
- Der Ersteller der Methode muss genau wissen, ob irgendwo anders in der Klassenhierarchie
__setattr__ebenfalls für einen bestimmten Zweck abgefangen wurde. Wenn ja, muss sie explizit diese Methode aufrufen, anstatt dem Wörterbuch zuzuweisen. Es gibt viele verschiedene Gründe,__setattr__zu überladen, sodass es ein erhebliches Konfliktpotenzial gibt. Zum Beispiel überladen Objektdatenbankimplementierungen oft setattr für einen völlig unrelated Zweck. - Die stringbasierte switch-Anweisung erzwingt, dass alle Attributhandler an einer Stelle im Code angegeben werden müssen. Sie können dann zu aufgabenspezifischen Methoden (für Modularität) weiterleiten, dies kann jedoch zu Leistungsproblemen führen.
- Die Logik für das Setzen, Abrufen und Löschen muss in
__getattr__,__setattr__und__delattr__leben. Auch dies kann durch eine zusätzliche Methodenebene gemildert werden, dies ist jedoch ineffizient.
Vorgeschlagene Syntax
Spezielle Methoden sollten sich mit Deklarationen der folgenden Form deklarieren
class x:
def __attr_XXX__(self, op, val ):
if op=="get":
return someComputedValue(self.internal)
elif op=="set":
self.internal=someComputedValue(val)
elif op=="del":
del self.internal
Client-Code sieht so aus
fooval=x.foo
x.foo=fooval+5
del x.foo
Semantik
Attributreferenzen aller drei Arten sollten die Methode aufrufen. Der op-Parameter kann "get"/"set"/"del" sein. Natürlich wird dieser String intern verarbeitet, sodass die tatsächlichen Prüfungen für den String sehr schnell sind.
Es ist verboten, tatsächlich ein Attribut namens XXX in derselben Instanz wie eine Methode namens __attr_XXX__ zu haben.
Eine Implementierung von __attr_XXX__ hat Vorrang vor einer Implementierung von __getattr__, nach dem Prinzip, dass __getattr__ nur aufgerufen werden soll, nachdem ein geeignetes Attribut nicht gefunden wurde.
Eine Implementierung von __attr_XXX__ hat Vorrang vor einer Implementierung von __setattr__, um konsistent zu sein. Die entgegengesetzte Wahl erscheint ebenfalls durchaus machbar. Dasselbe gilt für __del_y__.
Vorgeschlagene Implementierung
Es gibt einen neuen Objekttyp namens Attributzugriffs-Handler. Objekte dieses Typs haben die folgenden Attribute
name (e.g. XXX, not __attr__XXX__)
method (pointer to a method object)
In PyClass_New werden Methoden der entsprechenden Form erkannt und in Objekte umgewandelt (genau wie unverknüpfte Methodenobjekte). Diese werden im Klassen- __dict__ unter dem Namen XXX gespeichert. Die ursprüngliche Methode wird als unverknüpfte Methode unter ihrem ursprünglichen Namen gespeichert.
Wenn überhaupt Attributzugriffs-Handler in einer Instanz vorhanden sind, wird ein Flag gesetzt. Nennen wir es vorerst „I_have_computed_attributes“. Abgeleitete Klassen erben das Flag von Basisklassen. Instanzen erben das Flag von Klassen.
Ein Abruf erfolgt wie üblich bis kurz bevor das Objekt zurückgegeben wird. Zusätzlich zur aktuellen Prüfung, ob das zurückgegebene Objekt eine Methode ist, würde es auch prüfen, ob ein zurückgegebenes Objekt ein Zugriffs-Handler ist. Wenn ja, würde es die Getter-Methode aufrufen und den Wert zurückgeben. Um einen Attributzugriffs-Handler zu entfernen, könnten Sie direkt mit dem Wörterbuch manipulieren.
Ein Set erfolgt durch Überprüfung des Flags „I_have_computed_attributes“. Wenn es nicht gesetzt ist, läuft alles wie heute. Wenn es gesetzt ist, müssen wir eine Wörterbuch-Abfrage für den angeforderten Objektnamen durchführen. Wenn es einen Attributzugriffs-Handler zurückgibt, rufen wir die Setter-Funktion mit dem Wert auf. Wenn es ein anderes Objekt zurückgibt, verwerfen wir das Ergebnis und fahren wie heute fort. Beachten Sie, dass das Vorhandensein eines Attributzugriffs-Handlers die Leistung des Attribut-"Setzens" für alle Sets auf einer bestimmten Instanz leicht beeinträchtigen wird, aber nicht mehr als heute mit __setattr__. Abrufe sind effizienter als heute mit __getattr__.
Das Flag I_have_computed_attributes soll die Leistungsminderung eines zusätzlichen „Gets“ pro „Set“ für Objekte eliminieren, die diese Funktion nicht nutzen. Die Überprüfung dieses Flags sollte nur minimale Leistungsauswirkungen für alle Objekte haben.
Die Implementierung des Löschens ist analog zur Implementierung des Setzens.
Vorbehalte
- Sie bemerken vielleicht, dass ich keine Logik vorgeschlagen habe, um das Flag I_have_computed_attributes auf dem neuesten Stand zu halten, wenn Attribute zum Wörterbuch der Instanz hinzugefügt und daraus entfernt werden. Dies ist konsistent mit dem aktuellen Python. Wenn Sie einer Instanz nach ihrer Verwendung eine
__setattr__-Methode hinzufügen, wird diese Methode nicht so funktionieren, als wäre sie zur "Kompilierungs"-Zeit verfügbar gewesen. Der Dynamismus ist wohl die zusätzliche Implementierungsanstrengung nicht wert. Dieses Snippet zeigt das aktuelle Verhalten>>> def prn(*args):print args >>> class a: ... __setattr__=prn >>> a().foo=5 (<__main__.a instance at 882890>, 'foo', 5) >>> class b: pass >>> bi=b() >>> bi.__setattr__=prn >>> b.foo=5
- Die Zuweisung zu __dict__["XXX"] kann den Attributzugriffs-Handler für __attr_XXX__ überschreiben. Typischerweise speichern die Zugriffs-Handler Informationen in privaten __XXX-Variablen ab
- Ein Attributzugriffs-Handler, der versucht, setattr oder getattr auf dem Objekt selbst aufzurufen, kann eine Endlosschleife verursachen (wie bei
__getattr__). Auch hier ist die Lösung die Verwendung einer speziellen (typischerweise privaten) Variable wie __XXX.
Hinweis
Der in PEP 252 beschriebene Deskriptor-Mechanismus ist leistungsfähig genug, um dies direkter zu unterstützen. Ein 'getset'-Konstruktor könnte der Sprache hinzugefügt werden, um dies zu ermöglichen
class C:
def get_x(self):
return self.__x
def set_x(self, v):
self.__x = v
x = getset(get_x, set_x)
Zusätzlicher syntaktischer Zucker könnte hinzugefügt werden, oder eine Namenskonvention könnte erkannt werden.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0213.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT