PEP 363 – Syntax für dynamischen Attributzugriff
- Autor:
- Ben North <ben at redfrontdoor.org>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 29. Jan 2007
- Post-History:
- 12. Feb 2007
Zusammenfassung
Dynamischer Attributzugriff ist derzeit über die Builtins „getattr“ und „setattr“ möglich. Das vorliegende PEP schlägt eine neue Syntax vor, um diesen Zugriff zu erleichtern und dem Coder beispielsweise zu ermöglichen, zu schreiben
x.('foo_%d' % n) += 1
z = y.('foo_%d' % n).('bar_%s' % s)
anstatt
attr_name = 'foo_%d' % n
setattr(x, attr_name, getattr(x, attr_name) + 1)
z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
Begründung
Wörterbuchzugriff und Indizierung haben beide eine benutzerfreundliche Aufrufsyntax: statt x.__getitem__(12) kann der Coder x[12] schreiben. Dies ermöglicht auch die Verwendung von indizierten Elementen in einer erweiterten Zuweisung, wie in „x[12] += 1“. Der vorliegende Vorschlag bringt diese Benutzerfreundlichkeit auch für den dynamischen Attributzugriff.
Attributzugriff ist derzeit auf zwei Arten möglich
- Wenn der Attributname zur Zeit des Codierens bekannt ist, kann der „.NAME“-Trailer verwendet werden, wie in
x.foo = 42 y.bar += 100
- Wenn der Attributname dynamisch zur Laufzeit berechnet wird, müssen die Builtins „getattr“ und „setattr“ verwendet werden.
x = getattr(y, 'foo_%d' % n) setattr(z, 'bar_%s' % s, 99)
Die „getattr“-Builtin erlaubt es dem Coder auch, einen Standardwert anzugeben, der zurückgegeben wird, falls das Objekt kein Attribut mit dem angegebenen Namen hat.
x = getattr(y, 'foo_%d' % n, 0)
Dieses PEP beschreibt eine neue Syntax für den dynamischen Attributzugriff – „x.(expr)“ – mit Beispielen im obigen Abstract.
(Die neue Syntax könnte auch die Bereitstellung eines Standardwerts im „get“-Fall ermöglichen, wie in
x = y.('foo_%d' % n, None)
Diese 2-Argument-Form des dynamischen Attributzugriffs wäre nicht als Ziel einer (erweiterten oder normalen) Zuweisung erlaubt. Die folgende „Diskussion“-Sektion enthält Meinungen speziell zur 2-Argument-Erweiterung.)
Schließlich kann die neue Syntax mit der „del“-Anweisung verwendet werden, wie in
del x.(attr_name)
Auswirkungen auf bestehenden Code
Die vorgeschlagene neue Syntax ist derzeit nicht gültig, daher wird die Bedeutung bestehender gut formatierter Programme durch diesen Vorschlag nicht verändert.
In allen „*.py“-Dateien der 2.5-Distribution gibt es rund 600 Verwendungen von „getattr“, „setattr“ oder „delattr“. Sie gliedern sich wie folgt auf (die Zahlen können aufgrund einer teilweise manuellen Inspektion leichte Abweichungen aufweisen).
c.300 uses of plain "getattr(x, attr_name)", which could be
replaced with the new syntax;
c.150 uses of the 3-argument form, i.e., with the default
value; these could be replaced with the 2-argument form
of the new syntax (the cases break down into c.125 cases
where the attribute name is a literal string, and c.25
where it's only known at run-time);
c.5 uses of the 2-argument form with a literal string
attribute name, which I think could be replaced with the
standard "x.attribute" syntax;
c.120 uses of setattr, of which 15 use getattr to find the
new value; all could be replaced with the new syntax,
the 15 where getattr is also involved would show a
particular increase in clarity;
c.5 uses which would have to stay as "getattr" because they
are calls of a variable named "getattr" whose default
value is the builtin "getattr";
c.5 uses of the 2-argument form, inside a try/except block
which catches AttributeError and uses a default value
instead; these could use 2-argument form of the new
syntax;
c.10 uses of "delattr", which could use the new syntax.
Als Beispiele könnte die Zeile
setattr(self, attr, change_root(self.root, getattr(self, attr)))
aus Lib/distutils/command/install.py umgeschrieben werden
self.(attr) = change_root(self.root, self.(attr))
und die Zeile
setattr(self, method_name, getattr(self.metadata, method_name))
aus Lib/distutils/dist.py umgeschrieben werden
self.(method_name) = self.metadata.(method_name)
Leistungsauswirkungen
Erste Pystone-Messungen sind nicht eindeutig, deuten aber auf eine mögliche Leistungsstrafe von etwa 1% im Pystone-Score mit der gepatchten Version hin. Ein Vorschlag ist, dass dies daran liegt, dass die längere Hauptschleife in ceval.c das Cache-Verhalten beeinträchtigt, dies wurde jedoch nicht bestätigt.
Andererseits deuten Messungen auf eine Beschleunigung von etwa 40-45% für den dynamischen Attributzugriff hin.
Fehlerfälle
Nur Zeichenketten sind als Attributnamen erlaubt, daher wird beispielsweise der folgende Fehler produziert
>>> x.(99) = 8
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: attribute name must be string, not 'int'
Dies wird von der bestehenden Funktion PyObject_GetAttr behandelt.
Entwurf einer Implementierung
Eine Entwurfs-Implementierung fügt eine neue Alternative zur „trailer“-Klausel in Grammar/Grammar hinzu; ein neuer AST-Typ, „DynamicAttribute“ in Python.asdl, mit entsprechenden Änderungen an symtable.c, ast.c und compile.c, sowie drei neuen Opcodes (load/store/del) mit entsprechenden Änderungen an opcode.h und ceval.c. Der Patch besteht aus ca. 180 zusätzlichen Zeilen im Kerncode und ca. 100 zusätzlichen Zeilen an Tests. Er ist als Sourceforge-Patch #1657573 [1] verfügbar.
Diskussion in Mailinglisten
Die erste Veröffentlichung dieses PEP in der Entwurfsform erfolgte am 20070209 an python-ideas [2], und die Resonanz war generell positiv. Das PEP wurde dann am 20070212 an python-dev [3] veröffentlicht, und eine interessante Diskussion folgte. Eine kurze Zusammenfassung
Anfänglich gab es eine vernünftige (aber nicht einstimmige) Unterstützung für die Idee, obwohl die genaue Wahl der Syntax auf eine gemischtere Resonanz stieß. Mehrere Leute meinten, der „.“ würde zu leicht übersehen, mit dem Ergebnis, dass die Syntax mit einem Methoden-/Funktionsaufruf verwechselt werden könnte. Ein paar alternative Syntaktiken wurden vorgeschlagen
obj.(foo)
obj.[foo]
obj.{foo}
obj{foo}
obj.*foo
obj->foo
obj<-foo
obj@[foo]
obj.[[foo]]
wobei „obj.[foo]“ als bevorzugte Variante hervorging. In dieser anfänglichen Diskussion wurde die 2-Argument-Form universell abgelehnt, so dass sie aus dem PEP genommen werden sollte.
Die Diskussion ging dann zurück zur Frage, ob dieses spezielle Merkmal genügend Nutzen bot, um eine neue Syntax zu rechtfertigen. Neben der Notwendigkeit, dass sich die Coder mit der neuen Syntax vertraut machen müssen, gäbe es auch das Problem der Abwärtskompatibilität – Code, der die neue Syntax verwendet, würde auf älteren Pythons nicht laufen.
Anstelle einer neuen Syntax wurde eine neue „Wrapper-Klasse“ vorgeschlagen, mit der folgenden Spezifikation / konzeptionellen Implementierung von Martin von Löwis vorgeschlagen
class attrs:
def __init__(self, obj):
self.obj = obj
def __getitem__(self, name):
return getattr(self.obj, name)
def __setitem__(self, name, value):
return setattr(self.obj, name, value)
def __delitem__(self, name):
return delattr(self, name)
def __contains__(self, name):
return hasattr(self, name)
Dies wurde als sauberere und elegantere Lösung für das ursprüngliche Problem angesehen. (Ein weiterer Vorschlag war eine Mixin-Klasse, die einen attributähnlichen Zugriff auf die Attribute eines Objekts ermöglicht.)
Es wurde beschlossen, dass das vorliegende PEP die Beweislast für die Einführung einer neuen Syntax nicht erfüllt, eine Ansicht, die von Anfang der Diskussion an von einigen vertreten wurde. Die Wrapper-Klassen-Idee blieb als Möglichkeit für ein zukünftiges PEP offen.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0363.rst
Zuletzt geändert: 2024-04-14 20:08:31 GMT