Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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

Inhaltsverzeichnis

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.

  • dir zeigt 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_static zeigt möglicherweise nicht alle Attribute an

    Die Funktion inspect.getattr_static ruft 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 von inspect.getattr_static von dem von builtin.getattr unterscheiden kann.

  • inspect.getmembers und inspect.classify_class_attrs

    Beide 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.getmembers von pydoc verwendet 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_GETDESCRIPTOR nach 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

Referenzen

  • Issue 18181 (Bug-Bericht 18181) enthält eine veraltete Prototyp-Implementierung.

Quelle: https://github.com/python/peps/blob/main/peps/pep-0447.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT