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 用法的前提如下

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 实例”只是 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 的旧用法,可以在不显式创建 super 实例的情况下调用 MRO(方法解析顺序)中的下一个类(尽管通过 __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 列表的“Fixing super anyone?”线程中讨论过 [1]

未解决的问题

确定要使用的类对象

本 PEP 未指定将方法与定义类关联的确切机制,应选择性能最佳的机制。对于 CPython,建议将类实例保存在函数对象的 C 级变量中,该变量绑定到 NULL(不属于类)、Py_None(静态方法)或类对象(实例或类方法)之一。

super 真的应该成为一个关键字吗?

根据这项提议,super 将在与 None 相同的程度上成为关键字。进一步限制 super 名称可能会简化实现,但有些人反对将 super 实际关键字化。最简单的解决方案通常是正确的解决方案,而最简单的解决方案很可能不是在不需要时向语言添加额外的关键字。尽管如此,它可能会解决其他未解决的问题。

已关闭的问题

super 与 __call__ 属性一起使用

曾有考虑过以经典方式实例化 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.foo(self, *args)

这种变体实际上消除了定位正确实例的问题,如果任何替代方案受到关注,我希望是这一种。

super 或 super()

此提案没有为不同名称、签名或应用于其他类或实例留下任何余地。允许与正常提案同时进行某些类似用法的方法将是有利的,从而鼓励多重继承树和兼容方法的良好设计。

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

最后修改: 2025-02-01 08:59:27 GMT