PEP 367 – 新的 Super
- 作者:
- Calvin Spealman <ironfroggy at gmail.com>,Tim Delaney <timothy.c.delaney at gmail.com>
- 状态:
- 已取代
- 类型:
- 标准轨迹
- 创建:
- 2007年4月28日
- Python 版本:
- 2.6
- 历史记录:
- 2007年4月28日,2007年4月29日,2007年4月29日,2007年5月14日
编号说明
此 PEP 已重新编号为 PEP 3135。以下文本是根据旧编号提交的最后一个版本。
摘要
此 PEP 提出了一种语法糖,用于使用 super
类型来自动构建上级类型的实例,并绑定到定义方法的类以及方法当前正在作用的实例(或类方法的类对象)。
建议的新 super 用法的基本原理如下
super.foo(1, 2)
替换旧的
super(Foo, self).foo(1, 2)
以及当前的 __builtin__.super
将被重命名为 __builtin__.__super__
(__builtin__.super
将在 Python 3.0 中移除)。
此外,还建议将 super
赋值设为 SyntaxError
,类似于 None
的行为。
基本原理
当前的 super 用法需要显式传递它必须从中操作的类和实例,这需要违反 DRY(不要重复自己)规则。这阻碍了任何类名称的更改,并且许多人认为这是一种缺点。
规范
在规范部分,将使用一些特殊的术语来区分类似和密切相关的概念。“上级类型”将指代名为“super”的实际内置类型。“super 实例”只是上级类型的实例,它与一个类以及可能与该类的实例相关联。
由于新的 super
语义与 Python 2.5 不兼容,因此新的语义需要 __future__
导入
from __future__ import new_super
当前的 __builtin__.super
将被重命名为 __builtin__.__super__
。无论新的 super
语义是否处于活动状态,这都会发生。无法简单地重命名 __builtin__.super
,因为这会影响不使用新的 super
语义的模块。在 Python 3.0 中,建议删除名称 __builtin__.super
。
替换旧的 super 用法后,可以对 MRO(方法解析顺序)中的下一个类进行调用,而无需显式创建 super
实例(尽管仍然可以通过 __super__
支持这样做)。每个函数都将有一个名为 super
的隐式局部变量。此名称的行为与普通局部变量相同,包括通过单元格由内部函数使用,但以下例外情况除外
- 将
super
赋值将导致在编译时引发SyntaxError
; - 调用访问
super
名称的静态方法或普通函数将在运行时引发TypeError
。
每个使用 super
名称或具有使用 super
名称的内部函数的函数都将包含一个执行以下等效操作的序言
super = __builtin__.__super__(<class>, <instance>)
其中 <class>
是定义方法的类,而 <instance>
是方法的第一个参数(对于实例方法通常为 self
,对于类方法通常为 cls
)。对于静态方法和普通函数,<class>
将为 None
,导致在序言期间引发 TypeError
。
注意:super
和 __super__
之间的关系类似于 import
和 __import__
之间的关系。
python-dev 列表的主题“修复 super?” [1] 中讨论了其中大部分内容。
未解决的问题
确定要使用的类对象
此 PEP 中未指定将方法与定义类关联的确切机制,应选择该机制以实现最大性能。对于 CPython,建议将类实例保存在函数对象上的 C 级别变量中,该变量绑定到 NULL
(不是类的部分)、Py_None
(静态方法)或类对象(实例或类方法)之一。
super
应该真正成为关键字吗?
通过此提案,super
将成为与 None
程度相同的关键字。进一步限制 super
名称可能会简化实现,但是有些人反对将 super 实际关键字化。最简单的解决方案通常是正确的解决方案,最简单的解决方案很可能不是在不需要时向语言添加额外的关键字。尽管如此,它可能解决其他未解决的问题。
已解决的问题
使用 __call__ 属性的 super
有人认为,以经典方式实例化 super 实例可能是一个问题,因为调用它会查找 __call__ 属性,从而尝试对 MRO 中的下一个类执行自动 super 查找。但是,发现这是错误的,因为调用对象仅直接在对象的类型上查找 __call__ 方法。以下示例显示了这一点。
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__'
无论如何,随着将 __builtin__.super
重命名为 __builtin__.__super__
,此问题将完全消失。
参考实现
无法完全用 Python 实现上述规范。此参考实现与规范存在以下差异
- 新的
super
语义是使用字节码破解实现的。 super
的赋值不是SyntaxError
。另请参阅第 4 点。- 类必须使用元类
autosuper_meta
或继承自基类autosuper
以获取新的super
语义。 super
不是隐式局部变量。特别是,对于内部函数能够使用 super 实例,必须在方法中进行如下形式的赋值super = super
。
参考实现假设它正在 Python 2.5+ 上运行。
#!/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'
B.f 和 C.f 的反汇编显示了当 super
只是一个局部变量时与它被内部函数使用时使用的不同序言。
>>> 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
请注意,在最终实现中,序言不会成为方法的字节码的一部分,而是在解包参数后立即发生。
备选方案
无更改
尽管始终保持现状很有吸引力,但人们一直在寻求更改 super 的用法,并且有充分的理由,前面都提到了。
- 与类名称分离(该名称可能不再绑定到正确的类!)
- 更简单的外观,更简洁的 super 调用会更好
super 类型上的动态属性
该提案向 super 类型添加了动态属性查找,该属性将自动确定正确的类和实例参数。每次 super 属性查找都会识别这些参数并在实例上执行 super 查找,就像当前的 super 实现使用对类和实例的 super 实例的显式调用一样。
此提案依赖于 sys._getframe(),除了原型实现之外,不适合任何其他用途。
super(__this_class__, self)
这几乎是一个反提案,因为它基本上依赖于 __this_class__ PEP 的接受,该 PEP 提出了一种特殊名称,该名称将始终绑定到使用它的类。如果接受,则可以简单地使用 __this_class__ 而不是显式使用类名,从而解决名称绑定问题 [2]。
self.__super__.foo(*args)
此 PEP 在多个地方提到了 __super__ 属性,它可能是完整解决方案的候选者,实际上显式地使用它而不是直接使用任何 super。但是,双下划线名称通常是内部细节,并且试图将其排除在日常代码之外。
super(self, *args) 或 __super__(self, *args)
此解决方案仅解决了类型指示的问题,没有处理不同命名的 super 方法,并且明确说明了实例的名称。在无法对其他方法名称执行操作的情况下,它灵活性较差,在需要时会发生这种情况。此方案失败的一个用例是,基类具有工厂类方法,而子类具有两个工厂类方法,这两个类方法都需要正确地对基类中的一个进行 super 调用。
super.foo(self, *args)
这种变体实际上消除了查找正确实例的问题,如果任何备选方案被推到风口浪尖,我希望是这个。
super 或 super()
此提案不允许使用不同的名称、签名或应用于其他类或实例。允许在正常提案 alongside 旁边使用一些类似用途的方式将是有利的,鼓励良好设计多个继承树和兼容的方法。
super(*p, **kw)
有人提议直接调用super(*p, **kw)
等效于在与当前正在执行的方法同名的 super
对象上调用该方法,即以下两种方法等效
def f(self, *p, **kw):
super.f(*p, **kw)
def f(self, *p, **kw):
super(*p, **kw)
对此意见不一,但实现和风格问题是显而易见的。Guido 建议根据 KISS 原则(保持简单愚蠢)将此排除在此 PEP 之外。
历史
- 2007 年 4 月 29 日 - 将标题从“Super As A Keyword”更改为“New Super”
- 更新了大部分语言,并添加了一个术语部分,以在令人困惑的地方进行澄清。
- 添加了参考实现和历史记录部分。
- 2007 年 5 月 6 日 - 由 Tim Delaney 更新,以反映 python-3000 上的讨论
- 和 python-dev 邮件列表。
参考文献
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0367.rst