PEP 447 – Hinzufügen der __getdescriptor__-Methode zu Metaklassen
- Autor:
- Ronald Oussoren <ronaldoussoren at mac.com>
- Status:
- Verschoben
- Typ:
- Standards Track
- Erstellt:
- 12. Juni 2013
- Post-History:
- 02. Juli 2013, 15. Juli 2013, 29. Juli 2013, 22. Juli 2015
Zusammenfassung
Aktuell schauen object.__getattribute__ und super.__getattribute__ in das __dict__ von Klassen in der MRO für eine Klasse, wenn sie nach einem Attribut suchen. Diese PEP fügt eine optionale __getdescriptor__-Methode zu einer Metaklasse hinzu, die dieses Verhalten ersetzt und mehr Kontrolle über die Attributsuche gibt, insbesondere bei Verwendung eines super-Objekts.
Das heißt, die MRO-Durchlaufschleife in _PyType_Lookup und super.__getattribute__ wird von
def lookup(mro_list, name):
for cls in mro_list:
if name in cls.__dict__:
return cls.__dict__
return NotFound
zu
def lookup(mro_list, name):
for cls in mro_list:
try:
return cls.__getdescriptor__(name)
except AttributeError:
pass
return NotFound
Die Standardimplementierung von __getdescriptor__ schaut im Klassenverzeichnis nach
class type:
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name]
except KeyError:
raise AttributeError(name) from None
PEP Status
Diese PEP ist zurückgestellt, bis jemand Zeit hat, diese PEP zu aktualisieren und voranzutreiben.
Begründung
Es ist derzeit nicht möglich, zu beeinflussen, wie die Superklasse Attribute sucht (d. h. super.__getattribute__ schaut bedingungslos in das Klassen-__dict__), und das kann problematisch für dynamische Klassen sein, die nach Bedarf neue Methoden hinzufügen können, zum Beispiel dynamische Proxy-Klassen.
Die Methode __getdescriptor__ ermöglicht es, Attribute dynamisch hinzuzufügen, auch wenn sie mit der Superklasse gesucht werden.
Die neue Methode betrifft object.__getattribute__ (und PyObject_GenericGetAttr) aus Konsistenzgründen und um einen einzigen Ort für die Implementierung dynamischer Attributauflösung für Klassen zu haben.
Hintergrund
Das aktuelle Verhalten von super.__getattribute__ verursacht Probleme für Klassen, die dynamische Proxys für andere (nicht-Python) Klassen oder Typen sind, ein Beispiel dafür ist PyObjC. PyObjC erstellt für jede Klasse in der Objective-C-Laufzeit eine Python-Klasse und sucht nach Methoden in der Objective-C-Laufzeit, wenn sie verwendet werden. Dies funktioniert für normalen Zugriff gut, aber nicht für Zugriffe mit super-Objekten. Aus diesem Grund enthält PyObjC derzeit ein benutzerdefiniertes super, das mit seinen Klassen verwendet werden muss, sowie eine vollständige Neuentwicklung von PyObject_GenericGetAttr für normalen Attributzugriff.
Die API in dieser PEP ermöglicht die Entfernung des benutzerdefinierten super und vereinfacht die Implementierung, da das benutzerdefinierte Suchverhalten an einem zentralen Ort hinzugefügt werden kann.
Hinweis
PyObjC kann den Inhalt des Klassen-__dict__ nicht im Voraus berechnen, da Objective-C-Klassen zur Laufzeit neue Methoden erhalten können. Darüber hinaus enthalten Objective-C-Klassen tendenziell viele Methoden, während die meisten Python-Codes nur einen kleinen Teil davon verwenden, was die Vorausberechnung unnötig teuer macht.
Der Hook für die Superklassen-Attributsuche
Sowohl super.__getattribute__ als auch object.__getattribute__ (oder PyObject_GenericGetAttr und insbesondere _PyType_Lookup in C-Code) durchlaufen die MRO eines Objekts und schauen derzeit in das __dict__ der Klasse, um Attribute zu suchen.
Mit diesem Vorschlag schauen beide Suchmethoden nicht mehr in das Klassen-__dict__, sondern rufen die spezielle Methode __getdescriptor__ auf, die ein Slot auf der Metaklasse ist. Die Standardimplementierung dieser Methode sucht im Klassen-__dict__ nach, was bedeutet, dass die Attributsuche unverändert bleibt, es sei denn, ein Meta-Typ definiert tatsächlich die neue spezielle Methode.
Nebenbemerkung: Algorithmus zur Attributauflösung in Python
Der Attributauflösungsprozess, wie er von object.__getattribute__ (oder PyObject_GenericGetAttr in der CPython-Implementierung) implementiert wird, ist ziemlich einfach, aber ohne das Lesen von C-Code nicht ganz so einfach.
Die aktuelle CPython-Implementierung von object.__getattribute__ ist im Wesentlichen äquivalent zum folgenden (Pseudo-)Python-Code (ohne einige Aufräumarbeiten und Geschwindigkeitsoptimierungen)
def _PyType_Lookup(tp, name):
mro = tp.mro()
assert isinstance(mro, tuple)
for base in mro:
assert isinstance(base, type)
# PEP 447 will change these lines:
try:
return base.__dict__[name]
except KeyError:
pass
return None
class object:
def __getattribute__(self, name):
assert isinstance(name, str)
tp = type(self)
descr = _PyType_Lookup(tp, name)
f = None
if descr is not None:
f = descr.__get__
if f is not None and descr.__set__ is not None:
# Data descriptor
return f(descr, self, type(self))
dict = self.__dict__
if dict is not None:
try:
return self.__dict__[name]
except KeyError:
pass
if f is not None:
# Non-data descriptor
return f(descr, self, type(self))
if descr is not None:
# Regular class attribute
return descr
raise AttributeError(name)
class super:
def __getattribute__(self, name):
assert isinstance(name, unicode)
if name != '__class__':
starttype = self.__self_type__
mro = startype.mro()
try:
idx = mro.index(self.__thisclass__)
except ValueError:
pass
else:
for base in mro[idx+1:]:
# PEP 447 will change these lines:
try:
descr = base.__dict__[name]
except KeyError:
continue
f = descr.__get__
if f is not None:
return f(descr,
None if (self.__self__ is self.__self_type__) else self.__self__,
starttype)
else:
return descr
return object.__getattribute__(self, name)
Diese PEP sollte die Dict-Suche an den Zeilen, die mit "# PEP 447" beginnen, durch einen Methodenaufruf ersetzen, um die eigentliche Suche durchzuführen, was es ermöglicht, diese Suche sowohl für normalen Attributzugriff als auch für den Zugriff über den super-Proxy zu beeinflussen.
Beachten Sie, dass spezifische Klassen das Standardverhalten bereits vollständig überschreiben können, indem sie ihren eigenen __getattribute__-Slot implementieren (mit oder ohne Aufruf der Superklassen-Implementierung).
In Python-Code
Ein Meta-Typ kann eine Methode __getdescriptor__ definieren, die während der Attributauflösung sowohl von super.__getattribute__ als auch von object.__getattribute aufgerufen wird.
class MetaType(type):
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name]
except KeyError:
raise AttributeError(name) from None
Die Methode __getdescriptor__ hat als Argumente eine Klasse (die eine Instanz des Meta-Typs ist) und den Namen des Attributs, das gesucht wird. Sie sollte den Wert des Attributs zurückgeben, ohne Deskriptoren aufzurufen, und AttributeError auslösen, wenn der Name nicht gefunden werden kann.
Die type-Klasse bietet eine Standardimplementierung für __getdescriptor__, die den Namen im Klassenverzeichnis nachschlägt.
Beispielhafte Nutzung
Der folgende Code implementiert eine alberne Metaklasse, die die Attributsuche zu Großbuchstabenversionen von Namen umleitet.
class UpperCaseAccess (type):
def __getdescriptor__(cls, name):
try:
return cls.__dict__[name.upper()]
except KeyError:
raise AttributeError(name) from None
class SillyObject (metaclass=UpperCaseAccess):
def m(self):
return 42
def M(self):
return "fortytwo"
obj = SillyObject()
assert obj.m() == "fortytwo"
Wie bereits in dieser PEP erwähnt, ist ein realistischerer Anwendungsfall für diese Funktionalität eine Methode __getdescriptor__, die das Klassen-__dict__ basierend auf dem Attributzugriff dynamisch befüllt, insbesondere wenn die Klasse-__dict__ nicht zuverlässig mit ihrer Quelle synchron gehalten werden kann, zum Beispiel weil die Quelle, die zum Befüllen der __dict__ verwendet wird, ebenfalls dynamisch ist und keine Auslöser hat, die zur Erkennung von Änderungen an dieser Quelle verwendet werden können.
Ein Beispiel dafür sind die Klassenbrücken in PyObjC: Die Klassenbrücke ist ein Python-Objekt (Klasse), das eine Objective-C-Klasse darstellt und konzeptionell eine Python-Methode für jede Objective-C-Methode in der Objective-C-Klasse hat. Wie bei Python ist es möglich, neue Methoden zu einer Objective-C-Klasse hinzuzufügen oder bestehende zu ersetzen, und es gibt keine Rückrufe, die zur Erkennung davon verwendet werden können.
In C-Code
Ein neues Typflag Py_TPFLAGS_GETDESCRIPTOR mit dem Wert (1UL << 11), das anzeigt, dass der neue Slot vorhanden ist und verwendet werden soll.
Ein neuer Slot tp_getdescriptor wird zur PyTypeObject-Struktur hinzugefügt. Dieser Slot entspricht der __getdescriptor__-Methode auf type.
Der Slot hat den folgenden Prototyp.
PyObject* (*getdescriptorfunc)(PyTypeObject* cls, PyObject* name);
Diese Methode sollte name im Namensraum von cls suchen, ohne Superklassen zu betrachten, und keine Deskriptoren aufrufen. Die Methode gibt NULL zurück, ohne eine Ausnahme auszulösen, wenn der name nicht gefunden werden kann, und gibt andernfalls eine neue Referenz zurück (keine geliehene Referenz).
Klassen mit einem tp_getdescriptor-Slot müssen Py_TPFLAGS_GETDESCRIPTOR zu tp_flags hinzufügen, um anzuzeigen, dass der neue Slot verwendet werden muss.
Verwendung dieses Hooks durch den Interpreter
Die neue Methode ist für Metatypen erforderlich und wird daher auf type_ definiert. Sowohl super.__getattribute__ als auch object.__getattribute__/PyObject_GenericGetAttr (über _PyType_Lookup) verwenden diese __getdescriptor__-Methode beim Durchlaufen der MRO.
Weitere Änderungen an der Implementierung
Die Änderung für PyObject_GenericGetAttr wird durch die Änderung der privaten Funktion _PyType_Lookup vorgenommen. Diese gibt derzeit eine geliehene Referenz zurück, muss aber eine neue Referenz zurückgeben, wenn die __getdescriptor__-Methode vorhanden ist. Aus diesem Grund wird _PyType_Lookup in _PyType_LookupName umbenannt. Dies führt zu Compile-Fehlern für alle externen Benutzer dieser privaten API.
Aus demselben Grund wird _PyType_LookupId in _PyType_LookupId2 umbenannt. Eine Reihe anderer Funktionen in typeobject.c mit demselben Problem erhalten keinen aktualisierten Namen, da sie für diese Datei privat sind.
Der Attributsuche-Cache in Objects/typeobject.c wird für Klassen deaktiviert, die eine Metaklasse haben, die __getdescriptor__ überschreibt, da die Verwendung des Caches für solche Klassen möglicherweise nicht gültig ist.
Auswirkungen dieser PEP auf die Introspektion
Die Verwendung der in dieser PEP eingeführten Methode kann die Introspektion von Klassen mit einer Metaklasse, die eine benutzerdefinierte __getdescriptor__-Methode verwendet, beeinträchtigen. Dieser Abschnitt listet diese Änderungen auf.
Die unten aufgeführten Elemente sind nur von benutzerdefinierten __getdescriptor__-Methoden betroffen. Die Standardimplementierung für object verursacht keine Probleme, da diese immer noch nur die Klassen-__dict__ verwendet und keine sichtbaren Änderungen am sichtbaren Verhalten von object.__getattribute__ verursacht.
dirzeigt möglicherweise nicht alle Attribute anÄhnlich wie bei einer benutzerdefinierten
__getattribute__-Methode zeigt dir() möglicherweise nicht alle (Instanz-)Attribute an, wenn die Methode__getdescriptor__()zur dynamischen Auflösung von Attributen verwendet wird.Die Lösung dafür ist recht einfach: Klassen, die
__getdescriptor__verwenden, sollten auch __dir__() implementieren, wenn sie die vollständige Unterstützung für die integrierte Funktion dir() wünschen.inspect.getattr_staticzeigt möglicherweise nicht alle Attribute anDie Funktion
inspect.getattr_staticruft absichtlich keine__getattribute__und Deskriptoren auf, um die Ausführung von Benutzercode während der Introspektion mit dieser Funktion zu vermeiden. Die__getdescriptor__-Methode wird ebenfalls ignoriert und ist eine weitere Möglichkeit, wie sich das Ergebnis voninspect.getattr_staticvon dem vonbuiltin.getattrunterscheiden kann.inspect.getmembersundinspect.classify_class_attrsBeide dieser Funktionen greifen direkt auf das Klassen-__dict__ von Klassen entlang der MRO zu und können daher von einer benutzerdefinierten
__getdescriptor__-Methode betroffen sein.Code mit einer benutzerdefinierten
__getdescriptor__-Methode, der mit diesen Funktionen gut zusammenarbeiten soll, muss auch sicherstellen, dass die__dict__korrekt eingerichtet ist, wenn sie direkt von Python-Code aufgerufen wird.Beachten Sie, dass
inspect.getmembersvonpydocverwendet wird und dies daher die Laufzeit-Dokumentationsintrospektion beeinträchtigen kann.- Direkte Introspektion des Klassen-
__dict__Jeder Code, der direkt auf die Klassen-
__dict__für Introspektion zugreift, kann von einer benutzerdefinierten__getdescriptor__-Methode betroffen sein, siehe vorheriger Punkt.
Performance-Auswirkungen
WARNUNG: Die Benchmark-Ergebnisse in diesem Abschnitt sind alt und werden aktualisiert, sobald der Patch auf den aktuellen Trunk portiert wurde. Es werden keine signifikanten Änderungen an den Ergebnissen in diesem Abschnitt erwartet.
Mikro-Benchmarks
Issue 18181 (Bug-Bericht 18181) enthält als Anhang einen Mikro-Benchmark (pep447-micro-bench.py), der speziell die Geschwindigkeit der Attributsuche sowohl direkt als auch über super testet.
Beachten Sie, dass die Attributsuche mit tiefen Klassenhierarchien bei Verwendung einer benutzerdefinierten __getdescriptor__-Methode deutlich langsamer ist. Dies liegt daran, dass der Attributsuche-Cache für CPython bei Vorhandensein dieser Methode nicht verwendet werden kann.
Pybench
Die pybench-Ausgabe unten vergleicht eine Implementierung dieser PEP mit dem regulären Quellcode, beide basierend auf changeset a5681f50bae2, ausgeführt auf einer leeren Maschine und einem Core i7 Prozessor unter Centos 6.4.
Obwohl die Maschine leer war, gab es deutliche Unterschiede zwischen den Läufen. Ich habe Unterschiede in der „Minimalzeit“ von -0,1 % bis +1,5 % gesehen, mit ähnlichen (aber leicht kleineren) Unterschieden in der Differenz der „Durchschnittszeit“.
-------------------------------------------------------------------------------
PYBENCH 2.1
-------------------------------------------------------------------------------
* using CPython 3.4.0a0 (default, Jul 29 2013, 13:01:34) [GCC 4.4.7 20120313 (Red Hat 4.4.7-3)]
* disabled garbage collection
* system check interval set to maximum: 2147483647
* using timer: time.perf_counter
* timer: resolution=1e-09, implementation=clock_gettime(CLOCK_MONOTONIC)
-------------------------------------------------------------------------------
Benchmark: pep447.pybench
-------------------------------------------------------------------------------
Rounds: 10
Warp: 10
Timer: time.perf_counter
Machine Details:
Platform ID: Linux-2.6.32-358.114.1.openstack.el6.x86_64-x86_64-with-centos-6.4-Final
Processor: x86_64
Python:
Implementation: CPython
Executable: /tmp/default-pep447/bin/python3
Version: 3.4.0a0
Compiler: GCC 4.4.7 20120313 (Red Hat 4.4.7-3)
Bits: 64bit
Build: Jul 29 2013 14:09:12 (#default)
Unicode: UCS4
-------------------------------------------------------------------------------
Comparing with: default.pybench
-------------------------------------------------------------------------------
Rounds: 10
Warp: 10
Timer: time.perf_counter
Machine Details:
Platform ID: Linux-2.6.32-358.114.1.openstack.el6.x86_64-x86_64-with-centos-6.4-Final
Processor: x86_64
Python:
Implementation: CPython
Executable: /tmp/default/bin/python3
Version: 3.4.0a0
Compiler: GCC 4.4.7 20120313 (Red Hat 4.4.7-3)
Bits: 64bit
Build: Jul 29 2013 13:01:34 (#default)
Unicode: UCS4
Test minimum run-time average run-time
this other diff this other diff
-------------------------------------------------------------------------------
BuiltinFunctionCalls: 45ms 44ms +1.3% 45ms 44ms +1.3%
BuiltinMethodLookup: 26ms 27ms -2.4% 27ms 27ms -2.2%
CompareFloats: 33ms 34ms -0.7% 33ms 34ms -1.1%
CompareFloatsIntegers: 66ms 67ms -0.9% 66ms 67ms -0.8%
CompareIntegers: 51ms 50ms +0.9% 51ms 50ms +0.8%
CompareInternedStrings: 34ms 33ms +0.4% 34ms 34ms -0.4%
CompareLongs: 29ms 29ms -0.1% 29ms 29ms -0.0%
CompareStrings: 43ms 44ms -1.8% 44ms 44ms -1.8%
ComplexPythonFunctionCalls: 44ms 42ms +3.9% 44ms 42ms +4.1%
ConcatStrings: 33ms 33ms -0.4% 33ms 33ms -1.0%
CreateInstances: 47ms 48ms -2.9% 47ms 49ms -3.4%
CreateNewInstances: 35ms 36ms -2.5% 36ms 36ms -2.5%
CreateStringsWithConcat: 69ms 70ms -0.7% 69ms 70ms -0.9%
DictCreation: 52ms 50ms +3.1% 52ms 50ms +3.0%
DictWithFloatKeys: 40ms 44ms -10.1% 43ms 45ms -5.8%
DictWithIntegerKeys: 32ms 36ms -11.2% 35ms 37ms -4.6%
DictWithStringKeys: 29ms 34ms -15.7% 35ms 40ms -11.0%
ForLoops: 30ms 29ms +2.2% 30ms 29ms +2.2%
IfThenElse: 38ms 41ms -6.7% 38ms 41ms -6.9%
ListSlicing: 36ms 36ms -0.7% 36ms 37ms -1.3%
NestedForLoops: 43ms 45ms -3.1% 43ms 45ms -3.2%
NestedListComprehensions: 39ms 40ms -1.7% 39ms 40ms -2.1%
NormalClassAttribute: 86ms 82ms +5.1% 86ms 82ms +5.0%
NormalInstanceAttribute: 42ms 42ms +0.3% 42ms 42ms +0.0%
PythonFunctionCalls: 39ms 38ms +3.5% 39ms 38ms +2.8%
PythonMethodCalls: 51ms 49ms +3.0% 51ms 50ms +2.8%
Recursion: 67ms 68ms -1.4% 67ms 68ms -1.4%
SecondImport: 41ms 36ms +12.5% 41ms 36ms +12.6%
SecondPackageImport: 45ms 40ms +13.1% 45ms 40ms +13.2%
SecondSubmoduleImport: 92ms 95ms -2.4% 95ms 98ms -3.6%
SimpleComplexArithmetic: 28ms 28ms -0.1% 28ms 28ms -0.2%
SimpleDictManipulation: 57ms 57ms -1.0% 57ms 58ms -1.0%
SimpleFloatArithmetic: 29ms 28ms +4.7% 29ms 28ms +4.9%
SimpleIntFloatArithmetic: 37ms 41ms -8.5% 37ms 41ms -8.7%
SimpleIntegerArithmetic: 37ms 41ms -9.4% 37ms 42ms -10.2%
SimpleListComprehensions: 33ms 33ms -1.9% 33ms 34ms -2.9%
SimpleListManipulation: 28ms 30ms -4.3% 29ms 30ms -4.1%
SimpleLongArithmetic: 26ms 26ms +0.5% 26ms 26ms +0.5%
SmallLists: 40ms 40ms +0.1% 40ms 40ms +0.1%
SmallTuples: 46ms 47ms -2.4% 46ms 48ms -3.0%
SpecialClassAttribute: 126ms 120ms +4.7% 126ms 121ms +4.4%
SpecialInstanceAttribute: 42ms 42ms +0.6% 42ms 42ms +0.8%
StringMappings: 94ms 91ms +3.9% 94ms 91ms +3.8%
StringPredicates: 48ms 49ms -1.7% 48ms 49ms -2.1%
StringSlicing: 45ms 45ms +1.4% 46ms 45ms +1.5%
TryExcept: 23ms 22ms +4.9% 23ms 22ms +4.8%
TryFinally: 32ms 32ms -0.1% 32ms 32ms +0.1%
TryRaiseExcept: 17ms 17ms +0.9% 17ms 17ms +0.5%
TupleSlicing: 49ms 48ms +1.1% 49ms 49ms +1.0%
WithFinally: 48ms 47ms +2.3% 48ms 47ms +2.4%
WithRaiseExcept: 45ms 44ms +0.8% 45ms 45ms +0.5%
-------------------------------------------------------------------------------
Totals: 2284ms 2287ms -0.1% 2306ms 2308ms -0.1%
(this=pep447.pybench, other=default.pybench)
Ein Durchlauf der Benchmark-Suite (mit der Option "-b 2n3") scheint ebenfalls darauf hinzudeuten, dass die Leistungsauswirkungen minimal sind.
Report on Linux fangorn.local 2.6.32-358.114.1.openstack.el6.x86_64 #1 SMP Wed Jul 3 02:11:25 EDT 2013 x86_64 x86_64
Total CPU cores: 8
### call_method_slots ###
Min: 0.304120 -> 0.282791: 1.08x faster
Avg: 0.304394 -> 0.282906: 1.08x faster
Significant (t=2329.92)
Stddev: 0.00016 -> 0.00004: 4.1814x smaller
### call_simple ###
Min: 0.249268 -> 0.221175: 1.13x faster
Avg: 0.249789 -> 0.221387: 1.13x faster
Significant (t=2770.11)
Stddev: 0.00012 -> 0.00013: 1.1101x larger
### django_v2 ###
Min: 0.632590 -> 0.601519: 1.05x faster
Avg: 0.635085 -> 0.602653: 1.05x faster
Significant (t=321.32)
Stddev: 0.00087 -> 0.00051: 1.6933x smaller
### fannkuch ###
Min: 1.033181 -> 0.999779: 1.03x faster
Avg: 1.036457 -> 1.001840: 1.03x faster
Significant (t=260.31)
Stddev: 0.00113 -> 0.00070: 1.6112x smaller
### go ###
Min: 0.526714 -> 0.544428: 1.03x slower
Avg: 0.529649 -> 0.547626: 1.03x slower
Significant (t=-93.32)
Stddev: 0.00136 -> 0.00136: 1.0028x smaller
### iterative_count ###
Min: 0.109748 -> 0.116513: 1.06x slower
Avg: 0.109816 -> 0.117202: 1.07x slower
Significant (t=-357.08)
Stddev: 0.00008 -> 0.00019: 2.3664x larger
### json_dump_v2 ###
Min: 2.554462 -> 2.609141: 1.02x slower
Avg: 2.564472 -> 2.620013: 1.02x slower
Significant (t=-76.93)
Stddev: 0.00538 -> 0.00481: 1.1194x smaller
### meteor_contest ###
Min: 0.196336 -> 0.191925: 1.02x faster
Avg: 0.196878 -> 0.192698: 1.02x faster
Significant (t=61.86)
Stddev: 0.00053 -> 0.00041: 1.2925x smaller
### nbody ###
Min: 0.228039 -> 0.235551: 1.03x slower
Avg: 0.228857 -> 0.236052: 1.03x slower
Significant (t=-54.15)
Stddev: 0.00130 -> 0.00029: 4.4810x smaller
### pathlib ###
Min: 0.108501 -> 0.105339: 1.03x faster
Avg: 0.109084 -> 0.105619: 1.03x faster
Significant (t=311.08)
Stddev: 0.00022 -> 0.00011: 1.9314x smaller
### regex_effbot ###
Min: 0.057905 -> 0.056447: 1.03x faster
Avg: 0.058055 -> 0.056760: 1.02x faster
Significant (t=79.22)
Stddev: 0.00006 -> 0.00015: 2.7741x larger
### silent_logging ###
Min: 0.070810 -> 0.072436: 1.02x slower
Avg: 0.070899 -> 0.072609: 1.02x slower
Significant (t=-191.59)
Stddev: 0.00004 -> 0.00008: 2.2640x larger
### spectral_norm ###
Min: 0.290255 -> 0.299286: 1.03x slower
Avg: 0.290335 -> 0.299541: 1.03x slower
Significant (t=-572.10)
Stddev: 0.00005 -> 0.00015: 2.8547x larger
### threaded_count ###
Min: 0.107215 -> 0.115206: 1.07x slower
Avg: 0.107488 -> 0.115996: 1.08x slower
Significant (t=-109.39)
Stddev: 0.00016 -> 0.00076: 4.8665x larger
The following not significant results are hidden, use -v to show them:
call_method, call_method_unknown, chaos, fastpickle, fastunpickle, float, formatted_logging, hexiom2, json_load, normal_startup, nqueens, pidigits, raytrace, regex_compile, regex_v8, richards, simple_logging, startup_nosite, telco, unpack_sequence.
Alternative Vorschläge
__getattribute_super__
Eine frühere Version dieser PEP verwendete die folgende statische Methode auf Klassen.
def __getattribute_super__(cls, name, object, owner): pass
Diese Methode führte die Namenssuche sowie den Aufruf von Deskriptoren durch und war zwangsläufig darauf beschränkt, nur mit super.__getattribute__ zu arbeiten.
Wiederverwendung von tp_getattro
Es wäre wünschenswert, das Hinzufügen eines neuen Slots zu vermeiden, um die API einfacher und verständlicher zu halten. Ein Kommentar zu Issue 18181 (Bug-Bericht 18181) fragte nach der Wiederverwendung des tp_getattro-Slots, d. h. super könnte den tp_getattro-Slot aller Methoden entlang der MRO aufrufen.
Das funktioniert nicht, weil tp_getattro im Instanz-__dict__ sucht, bevor er versucht, Attribute mithilfe von Klassen in der MRO aufzulösen. Das würde bedeuten, dass die Verwendung von tp_getattro anstelle des Nachschlagens in den Klassenverzeichnissen die Semantik der Superklasse verändert.
Alternative Platzierung der neuen Methode
Diese PEP schlägt vor, __getdescriptor__ als Methode der Metaklasse hinzuzufügen. Eine Alternative wäre, sie als Klassenmethode der Klasse selbst hinzuzufügen (ähnlich wie __new__ ein staticmethod der Klasse und keine Methode der Metaklasse ist).
Der Vorteil der Verwendung einer Methode auf der Metaklasse besteht darin, dass ein Fehler auftritt, wenn zwei Klassen in der MRO unterschiedliche Metaklassen haben, die unterschiedliche Verhaltensweisen für __getdescriptor__ aufweisen können. Mit einer normalen Klassenmethode würde dieses Problem unbemerkt bleiben, könnte aber zu subtilen Fehlern während der Codeausführung führen.
Historie
- 23. Juli 2015: Hinzufügen des Typflags
Py_TPFLAGS_GETDESCRIPTORnach einem Gespräch mit Guido.Das neue Flag ist hauptsächlich nützlich, um Abstürze beim Laden einer Erweiterung für eine ältere Version von CPython zu vermeiden und könnte auch positive Geschwindigkeitsauswirkungen haben.
- Juli 2014: Umbenennung des Slots in
__getdescriptor__, der alte Name passte nicht zum Benennungsstil anderer Slots und war weniger beschreibend.
Diskussionsfäden
- Die erste Version der PEP wurde mit der Message-ID mailto:75030FAC-6918-4E94-95DA-67A88D53E6F5@mac.com gesendet.
- Weitere Diskussion beginnt mit einer Nachricht mit Message-ID mailto:5BB87CC4-F31B-4213-AAAC-0C0CE738460C@mac.com.
- Und mehr Diskussion beginnt mit einer Nachricht mit Message-ID mailto:00AA7433-C853-4101-9718-060468EBAC54@mac.com.
Referenzen
- Issue 18181 (Bug-Bericht 18181) enthält eine veraltete Prototyp-Implementierung.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0447.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT