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

Python Enhancement Proposals

PEP 367 – Neuer Super

Autor:
Calvin Spealman <ironfroggy at gmail.com>, Tim Delaney <timothy.c.delaney at gmail.com>
Status:
Abgelöst
Typ:
Standards Track
Erstellt:
28. Apr. 2007
Python-Version:
2.6
Post-History:
28-Apr-2007, 29-Apr-2007, 29-Apr-2007, 14-May-2007

Inhaltsverzeichnis

Nummerierungs-Hinweis

Dieses PEP wurde zu PEP 3135 – Neuer Super umbenannt. Der folgende Text ist die letzte Version, die unter der alten Nummer eingereicht wurde.

Zusammenfassung

Dieses PEP schlägt eine syntaktische Zuckerung für die Verwendung des super-Typs vor, um automatisch Instanzen des Supertyps zu erstellen, die an die Klasse, in der eine Methode definiert wurde, und an die Instanz (oder das Klassenobjekt für Klassenmethoden), auf die die Methode gerade wirkt, gebunden sind.

Die Prämisse der vorgeschlagenen neuen super-Verwendung ist wie folgt:

super.foo(1, 2)

das alte ersetzen

super(Foo, self).foo(1, 2)

und __builtin__.super sollte zu __builtin__.__super__ (wobei __builtin__.super in Python 3.0 entfernt werden soll) umbenannt werden.

Es wird ferner vorgeschlagen, dass die Zuweisung an super zu einem SyntaxError wird, ähnlich dem Verhalten von None.

Begründung

Die aktuelle Verwendung von super erfordert die explizite Übergabe sowohl der Klasse als auch der Instanz, von der aus sie operieren muss, was eine Verletzung der DRY-Regel (Don't Repeat Yourself) darstellt. Dies behindert jede Namensänderung der Klasse und wird oft von vielen als Schönheitsfehler angesehen.

Spezifikation

Im Spezifikationsabschnitt werden einige spezielle Terminologien verwendet, um ähnliche und eng verwandte Konzepte zu unterscheiden. „Supertyp“ bezieht sich auf den tatsächlichen eingebauten Typ namens „super“. Eine „Superinstanz“ ist einfach eine Instanz des Supertyps, die mit einer Klasse und möglicherweise mit einer Instanz dieser Klasse verbunden ist.

Da die neuen super-Semantiken nicht abwärtskompatibel mit Python 2.5 sind, erfordern die neuen Semantiken einen __future__-Import.

from __future__ import new_super

Das aktuelle __builtin__.super wird zu __builtin__.__super__ umbenannt. Dies geschieht unabhängig davon, ob die neuen super-Semantiken aktiv sind. Es ist nicht möglich, __builtin__.super einfach umzubenennen, da dies Module beeinträchtigen würde, die die neuen super-Semantiken nicht verwenden. In Python 3.0 wird vorgeschlagen, den Namen __builtin__.super zu entfernen.

Ersetzt die alte Verwendung von super. Aufrufe an die nächste Klasse in der MRO (Method Resolution Order) können ohne explizite Erstellung einer super-Instanz erfolgen (obwohl dies weiterhin über __super__ unterstützt wird). Jede Funktion erhält eine implizite lokale Variable namens super. Dieser Name verhält sich identisch zu einer normalen lokalen Variable, einschließlich der Verwendung durch innere Funktionen über eine Zelle, mit folgenden Ausnahmen:

  1. Die Zuweisung an den Namen super löst zur Kompilierzeit einen SyntaxError aus;
  2. Der Aufruf einer statischen Methode oder einer normalen Funktion, die auf den Namen super zugreift, löst zur Laufzeit einen TypeError aus.

Jede Funktion, die den Namen super verwendet oder eine innere Funktion hat, die den Namen super verwendet, wird eine Präambel enthalten, die das Äquivalent von Folgendem ausführt:

super = __builtin__.__super__(<class>, <instance>)

wobei <class> die Klasse ist, in der die Methode definiert wurde, und <instance> der erste Parameter der Methode ist (normalerweise self für Instanzmethoden und cls für Klassenmethoden). Für statische Methoden und normale Funktionen ist <class> None, was während der Präambel zu einem TypeError führt.

Hinweis: Die Beziehung zwischen super und __super__ ist ähnlich wie zwischen import und __import__.

Ein Großteil davon wurde im Thread der python-dev-Liste „Fixing super anyone?“ [1] diskutiert.

Offene Fragen

Bestimmung des zu verwendenden Klassenobjekts

Der genaue Mechanismus zur Verknüpfung der Methode mit der definierenden Klasse ist in diesem PEP nicht spezifiziert und sollte auf maximale Leistung ausgelegt werden. Für CPython wird vorgeschlagen, dass die Klasseninstanz in einer C-Level-Variable am Funktions-Objekt gehalten wird, die an NULL (nicht Teil einer Klasse), Py_None (statische Methode) oder ein Klassenobjekt (Instanz- oder Klassenmethode) gebunden ist.

Sollte super tatsächlich ein Schlüsselwort werden?

Mit diesem Vorschlag würde super ein Schlüsselwort werden, in demselben Maße, wie None ein Schlüsselwort ist. Es ist möglich, dass eine weitere Einschränkung des super-Namens die Implementierung vereinfacht, jedoch sind einige gegen die eigentliche Schlüsselwort-ifizierung von super. Die einfachste Lösung ist oft die richtige, und die einfachste Lösung ist möglicherweise nicht, zusätzliche Schlüsselwörter zur Sprache hinzuzufügen, wenn sie nicht benötigt werden. Dennoch könnte sie andere offene Probleme lösen.

Geschlossene Probleme

super mit __call__-Attributen verwenden

Es wurde in Betracht gezogen, dass das Instanziieren von super-Instanzen auf klassische Weise ein Problem darstellen könnte, da ein Aufruf das __call__-Attribut nachschlagen und somit versuchen würde, eine automatische super-Auflösung zur nächsten Klasse in der MRO durchzuführen. Dies erwies sich jedoch als falsch, da der Aufruf eines Objekts nur die __call__-Methode direkt im Typ des Objekts nachschlägt. Das folgende Beispiel zeigt dies in Aktion.

class A(object):
    def __call__(self):
        return '__call__'
    def __getattribute__(self, attr):
        if attr == '__call__':
            return lambda: '__getattribute__'
a = A()
assert a() == '__call__'
assert a.__call__() == '__getattribute__'

In jedem Fall löst die Umbenennung von __builtin__.super in __builtin__.__super__ dieses Problem vollständig.

Referenzimplementierung

Es ist unmöglich, die obige Spezifikation vollständig in Python zu implementieren. Diese Referenzimplementierung weist die folgenden Unterschiede zur Spezifikation auf:

  1. Die neuen super-Semantiken werden durch Bytecode-Manipulation implementiert.
  2. Die Zuweisung an super ist kein SyntaxError. Siehe auch Punkt #4.
  3. Klassen müssen entweder die Metaklasse autosuper_meta verwenden oder von der Basisklasse autosuper erben, um die neuen super-Semantiken zu erhalten.
  4. super ist keine implizite lokale Variable. Insbesondere damit innere Funktionen die super-Instanz verwenden können, muss eine Zuweisung der Form super = super in der Methode erfolgen.

Die Referenzimplementierung geht davon aus, dass sie auf Python 2.5+ läuft.

#!/usr/bin/env python
#
# autosuper.py

from array import array
import dis
import new
import types
import __builtin__
__builtin__.__super__ = __builtin__.super
del __builtin__.super

# We need these for modifying bytecode
from opcode import opmap, HAVE_ARGUMENT, EXTENDED_ARG

LOAD_GLOBAL = opmap['LOAD_GLOBAL']
LOAD_NAME = opmap['LOAD_NAME']
LOAD_CONST = opmap['LOAD_CONST']
LOAD_FAST = opmap['LOAD_FAST']
LOAD_ATTR = opmap['LOAD_ATTR']
STORE_FAST = opmap['STORE_FAST']
LOAD_DEREF = opmap['LOAD_DEREF']
STORE_DEREF = opmap['STORE_DEREF']
CALL_FUNCTION = opmap['CALL_FUNCTION']
STORE_GLOBAL = opmap['STORE_GLOBAL']
DUP_TOP = opmap['DUP_TOP']
POP_TOP = opmap['POP_TOP']
NOP = opmap['NOP']
JUMP_FORWARD = opmap['JUMP_FORWARD']
ABSOLUTE_TARGET = dis.hasjabs

def _oparg(code, opcode_pos):
    return code[opcode_pos+1] + (code[opcode_pos+2] << 8)

def _bind_autosuper(func, cls):
    co = func.func_code
    name = func.func_name
    newcode = array('B', co.co_code)
    codelen = len(newcode)
    newconsts = list(co.co_consts)
    newvarnames = list(co.co_varnames)

    # Check if the global 'super' keyword is already present
    try:
        sn_pos = list(co.co_names).index('super')
    except ValueError:
        sn_pos = None

    # Check if the varname 'super' keyword is already present
    try:
        sv_pos = newvarnames.index('super')
    except ValueError:
        sv_pos = None

    # Check if the cellvar 'super' keyword is already present
    try:
        sc_pos = list(co.co_cellvars).index('super')
    except ValueError:
        sc_pos = None

    # If 'super' isn't used anywhere in the function, we don't have anything to do
    if sn_pos is None and sv_pos is None and sc_pos is None:
        return func

    c_pos = None
    s_pos = None
    n_pos = None

    # Check if the 'cls_name' and 'super' objects are already in the constants
    for pos, o in enumerate(newconsts):
        if o is cls:
            c_pos = pos

        if o is __super__:
            s_pos = pos

        if o == name:
            n_pos = pos

    # Add in any missing objects to constants and varnames
    if c_pos is None:
        c_pos = len(newconsts)
        newconsts.append(cls)

    if n_pos is None:
        n_pos = len(newconsts)
        newconsts.append(name)

    if s_pos is None:
        s_pos = len(newconsts)
        newconsts.append(__super__)

    if sv_pos is None:
        sv_pos = len(newvarnames)
        newvarnames.append('super')

    # This goes at the start of the function. It is:
    #
    #   super = __super__(cls, self)
    #
    # If 'super' is a cell variable, we store to both the
    # local and cell variables (i.e. STORE_FAST and STORE_DEREF).
    #
    preamble = [
        LOAD_CONST, s_pos & 0xFF, s_pos >> 8,
        LOAD_CONST, c_pos & 0xFF, c_pos >> 8,
        LOAD_FAST, 0, 0,
        CALL_FUNCTION, 2, 0,
    ]

    if sc_pos is None:
        # 'super' is not a cell variable - we can just use the local variable
        preamble += [
            STORE_FAST, sv_pos & 0xFF, sv_pos >> 8,
        ]
    else:
        # If 'super' is a cell variable, we need to handle LOAD_DEREF.
        preamble += [
            DUP_TOP,
            STORE_FAST, sv_pos & 0xFF, sv_pos >> 8,
            STORE_DEREF, sc_pos & 0xFF, sc_pos >> 8,
        ]

    preamble = array('B', preamble)

    # Bytecode for loading the local 'super' variable.
    load_super = array('B', [
        LOAD_FAST, sv_pos & 0xFF, sv_pos >> 8,
    ])

    preamble_len = len(preamble)
    need_preamble = False
    i = 0

    while i < codelen:
        opcode = newcode[i]
        need_load = False
        remove_store = False

        if opcode == EXTENDED_ARG:
            raise TypeError("Cannot use 'super' in function with EXTENDED_ARG opcode")

        # If the opcode is an absolute target it needs to be adjusted
        # to take into account the preamble.
        elif opcode in ABSOLUTE_TARGET:
            oparg = _oparg(newcode, i) + preamble_len
            newcode[i+1] = oparg & 0xFF
            newcode[i+2] = oparg >> 8

        # If LOAD_GLOBAL(super) or LOAD_NAME(super) then we want to change it into
        # LOAD_FAST(super)
        elif (opcode == LOAD_GLOBAL or opcode == LOAD_NAME) and _oparg(newcode, i) == sn_pos:
            need_preamble = need_load = True

        # If LOAD_FAST(super) then we just need to add the preamble
        elif opcode == LOAD_FAST and _oparg(newcode, i) == sv_pos:
            need_preamble = need_load = True

        # If LOAD_DEREF(super) then we change it into LOAD_FAST(super) because
        # it's slightly faster.
        elif opcode == LOAD_DEREF and _oparg(newcode, i) == sc_pos:
            need_preamble = need_load = True

        if need_load:
            newcode[i:i+3] = load_super

        i += 1

        if opcode >= HAVE_ARGUMENT:
            i += 2

    # No changes needed - get out.
    if not need_preamble:
        return func

    # Our preamble will have 3 things on the stack
    co_stacksize = max(3, co.co_stacksize)

    # Conceptually, our preamble is on the `def` line.
    co_lnotab = array('B', co.co_lnotab)

    if co_lnotab:
        co_lnotab[0] += preamble_len

    co_lnotab = co_lnotab.tostring()

    # Our code consists of the preamble and the modified code.
    codestr = (preamble + newcode).tostring()

    codeobj = new.code(co.co_argcount, len(newvarnames), co_stacksize,
                       co.co_flags, codestr, tuple(newconsts), co.co_names,
                       tuple(newvarnames), co.co_filename, co.co_name,
                       co.co_firstlineno, co_lnotab, co.co_freevars,
                       co.co_cellvars)

    func.func_code = codeobj
    func.func_class = cls
    return func

class autosuper_meta(type):
    def __init__(cls, name, bases, clsdict):
        UnboundMethodType = types.UnboundMethodType

        for v in vars(cls):
            o = getattr(cls, v)
            if isinstance(o, UnboundMethodType):
                _bind_autosuper(o.im_func, cls)

class autosuper(object):
    __metaclass__ = autosuper_meta

if __name__ == '__main__':
    class A(autosuper):
        def f(self):
            return 'A'

    class B(A):
        def f(self):
            return 'B' + super.f()

    class C(A):
        def f(self):
            def inner():
                return 'C' + super.f()

            # Needed to put 'super' into a cell
            super = super
            return inner()

    class D(B, C):
        def f(self, arg=None):
            var = None
            return 'D' + super.f()

    assert D().f() == 'DBCA'

Die Disassemblierung von B.f und C.f zeigt die unterschiedlichen Präambeln, die verwendet werden, wenn super einfach eine lokale Variable ist, im Vergleich zu seiner Verwendung durch eine innere Funktion.

>>> dis.dis(B.f)

214           0 LOAD_CONST               4 (<type 'super'>)
              3 LOAD_CONST               2 (<class '__main__.B'>)
              6 LOAD_FAST                0 (self)
              9 CALL_FUNCTION            2
             12 STORE_FAST               1 (super)

215          15 LOAD_CONST               1 ('B')
             18 LOAD_FAST                1 (super)
             21 LOAD_ATTR                1 (f)
             24 CALL_FUNCTION            0
             27 BINARY_ADD
             28 RETURN_VALUE
>>> dis.dis(C.f)

218           0 LOAD_CONST               4 (<type 'super'>)
              3 LOAD_CONST               2 (<class '__main__.C'>)
              6 LOAD_FAST                0 (self)
              9 CALL_FUNCTION            2
             12 DUP_TOP
             13 STORE_FAST               1 (super)
             16 STORE_DEREF              0 (super)

219          19 LOAD_CLOSURE             0 (super)
             22 LOAD_CONST               1 (<code object inner at 00C160A0, file "autosuper.py", line 219>)
             25 MAKE_CLOSURE             0
             28 STORE_FAST               2 (inner)

223          31 LOAD_FAST                1 (super)
             34 STORE_DEREF              0 (super)

224          37 LOAD_FAST                2 (inner)
             40 CALL_FUNCTION            0
             43 RETURN_VALUE

Beachten Sie, dass die Präambel in der endgültigen Implementierung nicht Teil des Bytecodes der Methode wäre, sondern unmittelbar nach dem Entpacken der Parameter erfolgen würde.

Alternative Vorschläge

Keine Änderungen

Obwohl es immer attraktiv ist, die Dinge so zu belassen, wie sie sind, haben die Leute aus guten Gründen, die zuvor erwähnt wurden, schon seit einiger Zeit nach einer Änderung in der Verwendung von super-Aufrufen gesucht.

  • Entkopplung vom Klassennamen (der möglicherweise nicht einmal mehr an die richtige Klasse gebunden ist!)
  • Einfacher aussehende, sauberere super-Aufrufe wären besser.

Dynamisches Attribut vom Supertyp

Der Vorschlag fügt dem super-Typ ein dynamisches Attribut-Lookup hinzu, das automatisch die korrekten Klassen- und Instanzparameter bestimmt. Jeder super-Attribut-Lookup identifiziert diese Parameter und führt den super-Lookup auf der Instanz durch, wie die aktuelle super-Implementierung mit der expliziten Invokation einer super-Instanz auf eine Klasse und Instanz tut.

Dieser Vorschlag stützt sich auf sys._getframe(), was für alles außer einer Prototypimplementierung nicht geeignet ist.

super(__this_class__, self)

Dies ist fast ein Anti-Vorschlag, da er im Grunde auf der Akzeptanz des __this_class__ PEP basiert, das einen speziellen Namen vorschlägt, der immer an die Klasse gebunden wäre, innerhalb derer er verwendet wird. Wenn dies akzeptiert wird, könnte __this_class__ einfach anstelle des Klassennamens explizit verwendet werden, was die Probleme mit der Namensbindung löst [2].

self.__super__.foo(*args)

Das __super__-Attribut wird in diesem PEP an mehreren Stellen erwähnt und könnte ein Kandidat für die vollständige Lösung sein, indem man es tatsächlich explizit anstelle jeder direkten super-Verwendung verwendet. Double-Underscore-Namen sind jedoch normalerweise ein internes Detail und werden versucht, aus dem alltäglichen Code herausgehalten zu werden.

super(self, *args) oder __super__(self, *args)

Diese Lösung löst nur das Problem der Typangabe, behandelt keine unterschiedlich benannten super-Methoden und ist explizit bezüglich des Namens der Instanz. Sie ist weniger flexibel, wenn sie nicht auf andere Methodennamen angewendet werden kann, in Fällen, in denen dies erforderlich ist. Ein Anwendungsfall, den sie nicht abdeckt, ist, wenn eine Basisklasse eine Factory-Klassenmethode hat und eine Unterklasse zwei Factory-Klassenmethoden hat, die beide korrekt super-Aufrufe an die Basisklasse machen müssen.

super.foo(self, *args)

Diese Variante löst tatsächlich die Probleme bei der Lokalisierung der korrekten Instanz, und wenn eine der Alternativen in den Vordergrund gedrängt würde, würde ich wollen, dass es diese ist.

super oder super()

Dieser Vorschlag lässt keinen Raum für unterschiedliche Namen, Signaturen oder die Anwendung auf andere Klassen oder Instanzen. Eine Möglichkeit, eine ähnliche Verwendung neben dem normalen Vorschlag zu ermöglichen, wäre vorteilhaft, um ein gutes Design von Mehrfachvererbungshierarchien und kompatiblen Methoden zu fördern.

super(*p, **kw)

Es gab den Vorschlag, dass der direkte Aufruf von super(*p, **kw) äquivalent zum Aufruf der Methode auf dem super-Objekt mit demselben Namen wie die aktuell ausgeführte Methode wäre, d.h. die folgenden beiden Methoden wären äquivalent:

def f(self, *p, **kw):
    super.f(*p, **kw)
def f(self, *p, **kw):
    super(*p, **kw)

Es gibt hierfür starke Meinungen dafür und dagegen, aber Implementierungs- und Stilfragen sind offensichtlich. Guido hat vorgeschlagen, dass dies aus dem Prinzip KISS (Keep It Simple Stupid) von diesem PEP ausgeschlossen werden sollte.

Historie

29-Apr-2007 – Titel von „Super als Schlüsselwort“ in „Neuer Super“ geändert.
  • Viele Formulierungen aktualisiert und einen Terminologie-Abschnitt zur Klärung an verwirrenden Stellen hinzugefügt.
  • Referenzimplementierung und Historie hinzugefügt.
06-Mai-2007 – Aktualisiert von Tim Delaney, um Diskussionen auf den Mailinglisten python-3000 und python-dev widerzuspiegeln.
und python-dev Mailinglisten.

Referenzen


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

Zuletzt geändert: 2025-02-01 08:59:27 GMT