Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

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 的隐式局部变量。此名称的行为与普通局部变量相同,包括通过单元格由内部函数使用,但以下例外情况除外

  1. super 赋值将导致在编译时引发 SyntaxError
  2. 调用访问 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 实现上述规范。此参考实现与规范存在以下差异

  1. 新的 super 语义是使用字节码破解实现的。
  2. super 的赋值不是 SyntaxError。另请参阅第 4 点。
  3. 类必须使用元类 autosuper_meta 或继承自基类 autosuper 以获取新的 super 语义。
  4. 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

上次修改:2023-09-09 17:39:29 GMT