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
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:
- Die Zuweisung an den Namen
superlöst zur Kompilierzeit einenSyntaxErroraus; - Der Aufruf einer statischen Methode oder einer normalen Funktion, die auf den Namen
superzugreift, löst zur Laufzeit einenTypeErroraus.
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:
- Die neuen
super-Semantiken werden durch Bytecode-Manipulation implementiert. - Die Zuweisung an
superist keinSyntaxError. Siehe auch Punkt #4. - Klassen müssen entweder die Metaklasse
autosuper_metaverwenden oder von der Basisklasseautosupererben, um die neuensuper-Semantiken zu erhalten. superist keine implizite lokale Variable. Insbesondere damit innere Funktionen diesuper-Instanz verwenden können, muss eine Zuweisung der Formsuper = superin 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
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0367.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT