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

Python 增强提案

PEP 575 – 统一函数/方法类

作者:
Jeroen Demeyer <J.Demeyer at UGent.be>
状态:
已撤回
类型:
标准跟踪
创建日期:
2018年3月27日
Python 版本:
3.8
发布历史:
2018年3月31日, 2018年4月12日, 2018年4月27日, 2018年5月5日

目录

撤回通知

参阅 PEP 580,以获得允许自定义类快速调用的更好解决方案。

参阅 PEP 579,以更广泛地讨论本 PEP 中的其他一些问题。

摘要

重组函数和方法的类层次结构,目标是减小内置函数(用 C 实现)和 Python 函数之间的差异。主要是,在不牺牲性能的情况下,使内置函数更像 Python 函数。

引入了一个新的基类 `base_function`,各种函数类以及 `method`(重命名为 `bound_method`)都继承自它。

我们还允许对 Python 的 `function` 类进行子类化。

动机

目前,CPython 有两种不同的函数类:第一种是 Python 函数,这是通过 `def` 或 `lambda` 定义函数时得到的。第二种是内置函数,例如 `len`、`isinstance` 或 `numpy.dot`。这些是用 C 实现的。

这两个类完全独立实现,并且具有不同的功能。特别是,目前无法在 C 中高效地实现一个函数(只有内置函数可以做到),同时仍然允许进行自省,如 `inspect.signature` 或 `inspect.getsourcefile`(只有 Python 函数可以做到)。这对于像 Cython [1] 这样想要实现这一目标的项目来说是个问题。

在 Cython 中,通过发明一个新的函数类 `cyfunction` 来绕过这个问题。不幸的是,新的函数类会带来问题:`inspect` 模块不将此类函数识别为函数 [2],并且性能更差(CPython 对调用内置函数有特定的优化)。

第二个动机是更普遍地使内置函数和方法更像 Python 函数和方法。例如,Python 的未绑定方法只是函数,但扩展类型的未绑定方法(如 `dict.get`)是一个单独的类。Python 类的绑定方法有一个 `__func__` 属性,但扩展类型的绑定方法没有。

第三,本 PEP 允许对函数进行高度定制。`function` 类变得可以子类化,并且也允许为 C 实现的函数创建自定义函数子类。在后一种情况下,这可以以与内置函数相同的性能完成。所有函数都可以访问函数对象(`__call__` 中的 `self`),为 PEP 573 铺平道路。

新类

这是函数和方法的新类层次结构

              object
                 |
                 |
          base_function
         /       |     \
        /        |      \
       /         |   defined_function
      /          |        \
cfunction (*)    |         \
                 |       function
                 |
           bound_method (*)

标记为 (*) 的两个类**不允许**子类化;其他类则允许。

函数和未绑定方法之间没有区别,而绑定方法是 `bound_method` 的实例。

base_function

`base_function` 类成为所有函数类型的新基类。它基于现有的 `builtin_function_or_method` 类,但具有以下差异和新功能

  1. 它充当一个描述符,实现 `__get__`,将函数转换为方法(如果 `m_self` 是 `NULL`)。如果 `m_self` 不是 `NULL`,则这是一个空操作:而是返回现有函数。
  2. 一个新的只读属性 `__parent__`,在 C 结构中表示为 `m_parent`。如果此属性存在,则表示定义对象。对于扩展类型的实例方法,这是定义类(普通 Python 中的 `__class__`),对于模块的函数,这是定义模块。通常,它可以是任何 Python 对象。如果 `__parent__` 是一个类,它具有特殊的语义:在这种情况下,该函数必须使用 `self` 作为该类的实例来调用。最后,`__qualname__` 和 `__reduce__` 将使用 `__parent__` 作为命名空间(而不是之前的 `__self__`)。
  3. 一个新的属性 `__objclass__`,如果 `__parent__` 是一个类,则等于 `__parent__`。否则,访问 `__objclass__` 会引发 `AttributeError`。这旨在向后兼容 `method_descriptor`。
  4. 字段 `ml_doc` 以及属性 `__doc__` 和 `__text_signature__`(参见 参数クリニック)不受支持。
  5. `ml_flags` 的新标志 `METH_PASS_FUNCTION`。如果设置此标志,则存储在 `ml_meth` 中的 C 函数将使用一个额外的第一个参数(等于函数对象)来调用。
  6. `ml_flags` 的新标志 `METH_BINDING`,仅适用于模块的函数(而非类的实例方法)。如果设置此标志,则 `m_self` 将设置为 `NULL` 而不是模块。这允许函数更像 Python 函数,因为它启用了 `__get__`。
  7. 一个新标志 `METH_CALL_UNBOUND`,用于禁用 self slicing
  8. `ml_flags` 的新标志 `METH_PYTHON`。此标志表示此函数应被视为 Python 函数。理想情况下,应避免使用此标志,因为它违背了鸭子类型原则。但在某些地方仍需要它,例如 剖析

`base_function` 的目标是仅用一个结构支持调用函数和方法的所有不同方式。例如,新标志 `METH_PASS_FUNCTION` 将被方法实现使用。

无法直接创建 `base_function` 的实例(`tp_new` 是 `NULL`)。但是,C 代码可以手动创建实例。

这些是相关的 C 结构

PyTypeObject PyBaseFunction_Type;

typedef struct {
    PyObject_HEAD
    PyCFunctionDef *m_ml;     /* Description of the C function to call */
    PyObject *m_self;         /* __self__: anything, can be NULL; readonly */
    PyObject *m_module;       /* __module__: anything (typically str) */
    PyObject *m_parent;       /* __parent__: anything, can be NULL; readonly */
    PyObject *m_weakreflist;  /* List of weak references */
} PyBaseFunctionObject;

typedef struct {
    const char *ml_name;   /* The name of the built-in function/method */
    PyCFunction ml_meth;   /* The C function that implements it */
    int ml_flags;          /* Combination of METH_xxx flags, which mostly
                              describe the args expected by the C func */
} PyCFunctionDef;

子类可以通过额外的字段扩展 `PyCFunctionDef`。

Python 属性 `__self__` 返回 `m_self`,除非设置了 `METH_STATIC`。在这种情况下,或者如果 `m_self` 是 `NULL`,则根本没有 `__self__` 属性。因此,在本 PEP 中,我们使用 `m_self` 或 `__self__`,但含义略有不同。

cfunction

这是旧的 `builtin_function_or_method` 类的较新版本。选择名称 `cfunction` 是为了避免与“内置”混淆,因为“内置”指的是“`builtins` 模块中的某个东西”。它也更适合 C API,该 API 使用 `PyCFunction` 前缀。

`cfunction` 类是 `base_function` 的一个副本,但有以下区别

  1. `m_ml` 指向 `PyMethodDef` 结构,通过 `ml_doc` 字段扩展 `PyCFunctionDef`,以将 `__doc__` 和 `__text_signature__` 实现为只读属性
    typedef struct {
        const char *ml_name;
        PyCFunction ml_meth;
        int ml_flags;
        const char *ml_doc;
    } PyMethodDef;
    

    请注意,`PyMethodDef` 是 Python 稳定 ABI 的一部分,并且几乎被所有扩展模块使用,因此我们绝对不能更改此结构。

  2. 支持 参数クリニック
  3. `__self__` 始终存在。在 `base_function.__self__` 会引发 `AttributeError` 的情况下,则返回 `None`。

类型对象是 `PyTypeObject PyCFunction_Type`,我们将 `PyCFunctionObject` 定义为 `PyBaseFunctionObject` 的别名(`m_ml` 的类型除外)。

defined_function

`defined_function` 类是一个抽象基类,旨在表明该函数具有自省支持。`defined_function` 的实例需要支持 Python 函数的所有属性,即 `__code__`、`__globals__`、`__doc__`、`__defaults__`、`__kwdefaults__`、`__closure__` 和 `__annotations__`。还有一个 `__dict__` 来支持用户添加的属性。

这些属性中的任何一个都不必有意义。特别是,`__code__` 可能不是一个工作的代码对象,可能只填充了少数字段。本 PEP 没有规定这些属性是如何实现的。它们可以是简单的结构成员,也可以是更复杂的描述符。仅要求只读支持,不要求任何属性可写。

`defined_function` 类主要用于自动生成的 C 代码,例如由 Cython [1] 生成的代码。没有 API 可以创建其实例。

C 结构如下

PyTypeObject PyDefinedFunction_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *func_dict;        /* __dict__: dict or NULL */
} PyDefinedFunctionObject;

**待办**:也许为 `defined_function` 找到一个更好的名字。其他建议:`inspect_function`(任何满足 `inspect.isfunction` 的对象)、`builtout_function`(一个构建得更好的函数;双关语)、`generic_function`(原始提案,但与 `functools.singledispatch` 的泛型函数冲突)、`user_function`(由用户定义,与 CPython 相对)。

函数

这是用于 Python 实现的函数的类。与其他函数类型不同,`function` 的实例可以从 Python 代码创建。这一点没有改变,因此我们不在此 PEP 中描述细节。

C 结构的布局如下

PyTypeObject PyFunction_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *func_dict;        /* __dict__: dict or NULL */
    PyObject *func_code;        /* __code__: code */
    PyObject *func_globals;     /* __globals__: dict; readonly */
    PyObject *func_name;        /* __name__: string */
    PyObject *func_qualname;    /* __qualname__: string */
    PyObject *func_doc;         /* __doc__: can be anything or NULL */
    PyObject *func_defaults;    /* __defaults__: tuple or NULL */
    PyObject *func_kwdefaults;  /* __kwdefaults__: dict or NULL */
    PyObject *func_closure;     /* __closure__: tuple of cell objects or NULL; readonly */
    PyObject *func_annotations; /* __annotations__: dict or NULL */
    PyCFunctionDef _ml;         /* Storage for base.m_ml */
} PyFunctionObject;

描述符 `__name__` 返回 `func_name`。设置 `__name__` 时,还会使用 UTF-8 编码的名称更新 `base.m_ml->ml_name`。

`_ml` 字段保留了 `base.m_ml` 的空间。

`base_function` 实例必须具有 `METH_PYTHON` 标志,当且仅当它是 `function` 的实例。

当从 `code` 和 `globals` 构建 `function` 的实例时,将创建一个实例,其中 `base.m_ml = &_ml`,`base.m_self = NULL`。

为了使子类化更容易,我们还添加了一个复制构造函数:如果 `f` 是 `function` 的实例,则 `types.FunctionType(f)` 将复制 `f`。这可以方便地使用自定义函数类型作为装饰器

>>> from types import FunctionType
>>> class CustomFunction(FunctionType):
...     pass
>>> @CustomFunction
... def f(x):
...     return x
>>> type(f)
<class '__main__.CustomFunction'>

这也消除了 `functools.wraps` 的许多用例:包装器可以被 `function` 的子类替换。

bound_method

`bound_method` 类用于所有绑定方法,无论底层函数是什么类。它在 `base_function` 之上添加了一个新属性:`__func__` 指向该函数。

`bound_method` 替换了旧的 `method` 类,该类仅用于绑定为方法的 Python 函数。

有一个复杂之处,因为我们希望允许从任意可调用对象构建方法。这可能是一个已绑定的方法,或者根本不是 `base_function` 的实例。因此,实际上有两种方法

  • 对于任意可调用对象,我们使用单个固定的 `PyCFunctionDef` 结构,并将 `METH_PASS_FUNCTION` 标志设置为 `METH_PASS_FUNCTION`。
  • 对于绑定 `base_function` 实例(更准确地说,具有 `Py_TPFLAGS_BASEFUNCTION` 标志)且具有 self slicing 的方法,我们改用原始函数的 `PyCFunctionDef`。这样,我们在调用绑定方法时就不会丢失任何性能。在这种情况下,`__func__` 属性仅用于实现各种属性,而不是用于调用方法。

从 `base_function` 构建新方法时,我们检查 `self` 对象是否是 `__objclass__` 的实例(如果将类指定为父对象),否则引发 `TypeError`。

C 结构是

PyTypeObject PyMethod_Type;

typedef struct {
    PyBaseFunctionObject base;
    PyObject *im_func;  /* __func__: function implementing the method; readonly */
} PyMethodObject;

调用 base_function 实例

我们指定 `base_function` 实例的 `__call__` 的实现。

检查 __objclass__

首先,如果函数的 `__parent__` 是一个类(回想一下 `__objclass__` 此时成为 `__parent__` 的别名),则进行类型检查:如果 `m_self` 是 `NULL`(这是扩展类型的未绑定方法的案例),则函数必须至少使用一个位置参数调用,并且第一个参数(通常称为 `self`)必须是 `__objclass__` 的实例。否则,将引发 `TypeError`。

请注意,绑定方法具有 `m_self != NULL`,因此不检查 `__objclass__`。相反,`__objclass__` 检查是在构建方法时完成的。

标志

为了方便起见,我们定义了一个新的常量:`METH_CALLFLAGS` 结合了 `PyCFunctionDef.ml_flags` 中所有指定要调用的 C 函数签名的标志。它等于

METH_VARARGS | METH_FASTCALL | METH_NOARGS | METH_O | METH_KEYWORDS | METH_PASS_FUNCTION

上述前四个标志中必须正好设置一个,并且只有 `METH_VARARGS` 和 `METH_FASTCALL` 可以与 `METH_KEYWORDS` 组合。违反这些规则是未定义行为。

有两个新标志会影响函数调用,即 `METH_PASS_FUNCTION` 和 `METH_CALL_UNBOUND`。一些标志已在 [5] 中说明。我们将在下面解释其他标志。

Self slicing(自切片)

如果函数具有 `m_self == NULL` 并且未设置 `METH_CALL_UNBOUND` 标志,则第一个位置参数(如果有)将从 `*args` 中移除,并作为第一个参数传递给 C 函数。实际上,第一个位置参数被视为 `__self__`。这旨在支持未绑定方法,以便 C 函数看不到绑定和未绑定方法调用的区别。这不会以任何方式影响关键字参数。

此过程称为*self slicing*,如果 `m_self == NULL` 并且未设置 `METH_CALL_UNBOUND`,则称该函数*具有 self slicing*。

请注意,具有 self slicing 的 `METH_NOARGS` 函数实际上有一个参数,即 `self`。类似地,具有 self slicing 的 `METH_O` 函数有两个参数。

METH_PASS_FUNCTION

如果设置此标志,则 C 函数将使用一个额外的第一个参数调用,即函数本身(`base_function` 实例)。作为特例,如果函数是 `bound_method`,则传递方法的底层函数(但不是递归的:如果 `bound_method` 包装了 `bound_method`,则 `__func__` 只应用一次)。

例如,普通的 `METH_VARARGS` 函数的签名是 `(PyObject *self, PyObject *args)`。使用 `METH_VARARGS | METH_PASS_FUNCTION`,这将变为 `(PyObject *func, PyObject *self, PyObject *args)`。

METH_FASTCALL

这是一个现有但未记录的标志。我们建议正式支持并记录它。

如果设置了 `METH_FASTCALL` 标志而未设置 `METH_KEYWORDS`,则 `ml_meth` 字段的类型为 `PyCFunctionFast`,它接受参数 `(PyObject *self, PyObject *const *args, Py_ssize_t nargs)`。此类函数仅接受位置参数,并且这些参数以普通 C 数组 `args` 的形式传递,长度为 `nargs`。

如果设置了 `METH_FASTCALL | METH_KEYWORDS` 标志,则 `ml_meth` 字段的类型为 `PyCFunctionFastKeywords`,它接受参数 `(PyObject *self, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)`。位置参数以 C 数组 `args` 的形式传递,长度为 `nargs`。关键字参数的*值*紧随其后,从位置 `nargs` 开始。关键字参数的*键*(名称)作为元组传递给 `kwnames`。例如,假设提供了 3 个位置参数和 2 个关键字参数。然后 `args` 是一个长度为 3 + 2 = 5 的数组,`nargs` 等于 3,`kwnames` 是一个 2 元组。

内置函数的自动创建

Python 会自动为扩展类型(使用 `PyTypeObject.tp_methods` 字段)和模块(使用 `PyModuleDef.m_methods` 字段)生成 `cfunction` 的实例。数组 `PyTypeObject.tp_methods` 和 `PyModuleDef.m_methods` 必须是 `PyMethodDef` 结构的数组。

扩展类型的未绑定方法

未绑定方法的类型从 `method_descriptor` 更改为 `cfunction`。作为未绑定方法出现的对象与类 `__dict__` 中的对象相同。Python 会自动将 `__parent__` 属性设置为定义类。

模块的内置函数

对于模块函数的情况,`__parent__` 将设置为模块。除非给出 `METH_BINDING` 标志,否则 `__self__` 也将设置为模块(为了向后兼容)。

一个重要的后果是,此类函数默认情况下用作属性时不会绑定为方法(`base_function.__get__` 仅在 `m_self` 为 `NULL` 时才执行此操作)。有人可能认为这是一个错误,但这是为了向后兼容:在 python-ideas [6] 的一个早期帖子中,共识是保留内置函数的这个“错误特性”。

但是,为了允许特定或新实现的内置函数这样做,`METH_BINDING` 标志会阻止设置 `__self__`。

进一步的更改

新的类型标志

添加了一个新的 `PyTypeObject` 标志(用于 `tp_flags`):`Py_TPFLAGS_BASEFUNCTION`,以指示此类型的实例是函数,可以像 `base_function` 一样被调用和绑定为方法。

这与 `Py_TPFLAGS_LIST_SUBCLASS` 等标志不同,因为它表示的不仅仅是一个子类:它还表示 `__call__` 和 `__get__` 的默认实现。特别是,`base_function` 的此类子类必须遵循 Calling base_function instances 部分的实现。

对于继承了 `base_function` 的 `tp_call` 和 `tp_descr_get` 实现的扩展类型,此标志会自动设置。扩展类型可以通过显式指定它来指定,如果它们以兼容的方式覆盖 `__call__` 或 `__get__`。`Py_TPFLAGS_BASEFUNCTION` 标志绝不能为堆类型设置,因为那是不安全的(堆类型可以动态更改)。

C API 函数

我们列出了一些相关的 Python/C API 宏和函数。其中一些是现有的(可能已更改)函数,一些是新的

  • `int PyBaseFunction_CheckFast(PyObject *op)`:如果 `op` 是具有 `Py_TPFLAGS_BASEFUNCTION` 标志的类的实例,则返回 true。这是您需要使用以确定访问 `base_function` 内部是否具有意义的函数。
  • `int PyBaseFunction_Check(PyObject *op)`:如果 `op` 是 `base_function` 的实例,则返回 true。
  • `PyObject *PyBaseFunction_New(PyTypeObject *cls, PyCFunctionDef *ml, PyObject *self, PyObject *module, PyObject *parent)`:根据给定的数据创建一个 `cls`(必须是 `base_function` 的子类)的新实例。
  • `int PyCFunction_Check(PyObject *op)`:如果 `op` 是 `cfunction` 的实例,则返回 true。
  • `int PyCFunction_NewEx(PyMethodDef* ml, PyObject *self, PyObject* module)`:创建一个 `cfunction` 的新实例。作为特例,如果 `self` 是 `NULL`,则将其设置为 `Py_None`(为了向后兼容)。如果 `self` 是模块,则 `__parent__` 设置为 `self`。否则,`__parent__` 是 `NULL`。
  • 对于许多现有的 `PyCFunction_...` 和 `PyMethod_...` 函数,我们定义了一个新的函数 `PyBaseFunction_...` 来处理 `base_function` 实例。旧函数将保留为新函数的别名。
  • `int PyFunction_Check(PyObject *op)`:如果 `op` 是具有 `METH_PYTHON` 标志的 `base_function` 的实例,则返回 true(这等同于检查 `op` 是否是 `function` 的实例)。
  • `int PyFunction_CheckFast(PyObject *op)`:等同于 `PyFunction_Check(op) && PyBaseFunction_CheckFast(op)`。
  • `int PyFunction_CheckExact(PyObject *op)`:如果 `op` 的类型是 `function`,则返回 true。
  • `PyObject *PyFunction_NewPython(PyTypeObject *cls, PyObject *code, PyObject *globals, PyObject *name, PyObject *qualname)`:根据给定的数据创建一个 `cls`(必须是 `function` 的子类)的新实例。
  • `PyObject *PyFunction_New(PyObject *code, PyObject *globals)`:创建一个 `function` 的新实例。
  • `PyObject *PyFunction_NewWithQualName(PyObject *code, PyObject *globals, PyObject *qualname)`:创建一个 `function` 的新实例。
  • `PyObject *PyFunction_Copy(PyTypeObject *cls, PyObject *func)`:通过复制给定的 `function` 来创建一个 `cls`(必须是 `function` 的子类)的新实例。

对 types 模块的更改

添加了两个类型:`types.BaseFunctionType` 对应 `base_function`,`types.DefinedFunctionType` 对应 `defined_function`。

除此之外,`types` 模块没有其他更改。特别是,`types.FunctionType` 指的是 `function`。但是,实际类型将发生变化:特别是,`types.BuiltinFunctionType` 将不再与 `types.BuiltinMethodType` 相同。

对 inspect 模块的更改

新函数 `inspect.isbasefunction` 检查 `base_function` 的实例。

`inspect.isfunction` 检查 `defined_function` 的实例。

`inspect.isbuiltin` 检查 `cfunction` 的实例。

`inspect.isroutine` 检查 `isbasefunction` 或 `ismethoddescriptor`。

**注意**:bpo-33261 [3] 应首先修复。

分析

目前,`sys.setprofile` 支持内置函数的 `c_call`、`c_return` 和 `c_exception` 事件。这些事件在调用或返回内置函数时生成。相比之下,`call` 和 `return` 事件由函数本身生成。因此,对于 `call` 和 `return` 事件,无需更改。

由于我们不再区分 C 函数和 Python 函数,因此需要阻止为 Python 函数生成 `c_*` 事件。通过不生成这些事件(如果 `ml_flags` 中的 `METH_PYTHON` 标志已设置)来实现这一点。

非 CPython 实现

本 PEP 的大部分内容仅与 CPython 相关。对于其他 Python 实现,必需的两个更改是 `base_function` 基类以及 `function` 可以被子类化。`cfunction` 和 `defined_function` 类不是必需的。

我们出于一致性要求 `base_function`,但我们对其没有要求:如果它只是 `object` 的一个副本是可以接受的。对新的 `__parent__`(和 `__objclass__`)属性的支持不是必需的。如果没有 `defined_function` 类,那么 `types.DefinedFunctionType` 应该是 `types.FunctionType` 的别名。

基本原理

为什么不直接更改现有类?

人们可以尝试通过保留现有类而不引入新的 `base_function` 类来解决这个问题。

这可能看起来是一个更简单的解决方案,但事实并非如此:它需要对 3 个不同的类进行自省支持:`function`、`builtin_function_or_method` 和 `method_descriptor`。对于后两个类,“自省支持”至少意味着允许子类化。但我们不想丢失性能,所以我们想要快速的子类检查。这需要在 `tp_flags` 中添加两个新标志。而且我们希望子类允许内置函数的 `__get__`,因此我们也应该为内置函数实现 `LOAD_METHOD` 操作码。更一般地说,将需要复制大量功能,最终结果将是更复杂的代码。

同样不清楚内置函数子类的自省将如何与 `__text_signature__` 交互。在同一个类上拥有两种独立的 `inspect.signature` 支持听起来是在制造麻烦。

而且,这也不会解决在 motivation 中提到的一些内置函数和 Python 函数之间的其他差异。

为什么 __text_signature__ 不是解决方案

内置函数有一个 `__text_signature__` 属性,该属性以纯文本形式给出函数的签名。默认值由 `ast.literal_eval` 进行求值。因此,它只支持少量标准的 Python 类,而不支持任意的 Python 对象。

即使 `__text_signature__` 以某种方式允许任意签名,这也只是一部分自省:例如,它对 `inspect.getsourcefile` 没有帮助。

defined_function 与 function 的区别

在许多地方,需要决定是使用旧的 `function` 类替换为 `defined_function` 还是新的 `function` 类。这是通过考虑最可能的用例来完成的

  1. `types.FunctionType` 指的是 `function`,因为该类型可能用于通过 `types.FunctionType(...)` 来构造实例。
  2. `inspect.isfunction()` 指的是 `defined_function`,因为这是支持自省的类。
  3. C API 函数必须指 `function`,因为我们没有规定 `defined_function` 的各种属性是如何实现的。我们预计这不会成为问题,因为 C 扩展通常没有理由进行自省。

本 PEP 的范围:涉及哪些类?

本 PEP 的主要动机是修复函数类,因此我们当然希望统一现有的 `builtin_function_or_method` 和 `function` 类。

由于内置函数和方法具有相同的类,因此似乎自然地也包括绑定方法。而且由于 Python 函数没有“未绑定方法”,因此很有必要消除扩展类型的未绑定方法。

目前,不对 `staticmethod`、`classmethod` 和 `classmethod_descriptor` 类进行任何更改。将它们放入 `base_function` 类层次结构并统一 `classmethod` 和 `classmethod_descriptor` 当然是有意义的。然而,本 PEP 已经足够庞大,这留待将来可能的改进。

扩展类型的槽包装器(如 `__init__` 或 `__eq__`)与普通方法有很大的不同。它们通常也不会直接调用,因为你通常会写 `foo[i]` 而不是 `foo.__getitem__(i)`。因此,这些被排除在本 PEP 的范围之外。

Python 还有一个 `instancemethod` 类,这似乎是 Python 2 的遗留物,当时它用于绑定和未绑定方法。尚不清楚是否仍有其用例。无论如何,在本 PEP 中没有必要处理它。

**待办**:是否应该弃用 `instancemethod`?它似乎在 CPython 3.7 中根本没有被使用,但也许外部包会使用它?

不处理 METH_STATIC 和 METH_CLASS

本 PEP 中几乎没有内容涉及 `METH_STATIC` 和 `METH_CLASS` 标志。这些标志仅由 内置函数的自动创建 检查。当绑定 `staticmethod`、`classmethod` 或 `classmethod_descriptor`(即调用 `__get__`)时,会创建一个 `base_function` 实例,其中 `m_self != NULL`。对于 `classmethod`,这一点很明显,因为 `m_self` 是方法所绑定的类。对于 `staticmethod`,可以为 `m_self` 取任意 Python 对象。为了向后兼容,我们将扩展类型的静态方法的 `m_self` 设置为 `__parent__`。

base_function 中的 __self__

乍一看,在 `base_function` 中添加 `__self__` 槽而不是在 `bound_method` 中添加似乎很奇怪。我们借鉴了现有 `builtin_function_or_method` 类的这个想法。它允许我们为本 PEP 中讨论的各种函数类提供一个通用的 `__call__` 和 `__get__` 实现。

它还使得支持现有的内置函数(例如 `sys.exit.__self__` 是 `sys`)变得容易,这些函数将 `__self__` 设置为模块。

__doc__ 的两种实现

`base_function` 不支持函数文档字符串。相反,`cfunction` 和 `function` 类各自有处理文档字符串的方法(`bound_method` 仅从包装的函数中获取 `__doc__`)。

对于 `cfunction`,文档字符串(以及文本签名)存储在 `PyMethodDef` 的只读 `ml_doc` 字段中的 C 字符串中。对于 `function`,文档字符串存储为可写 Python 对象,实际上不必是字符串。统一处理 `__doc__` 的这两种非常不同的方式似乎很困难。为了向后兼容,我们保留现有实现。

对于 `defined_function`,我们要求实现 `__doc__`,但不指定如何实现。子类可以像 `cfunction` 一样实现 `__doc__`,或者使用结构成员或其他方式。

子类化

我们不允许子类化 `cfunction` 和 `bound_method`,以支持 `PyCFunction_Check` 和 `PyMethod_Check` 的快速类型检查。

我们允许子类化其他类,因为没有理由不允许。对于 Python 模块,唯一相关的类是 `function`,因为其他类无法实例化。

替换 tp_call:METH_PASS_FUNCTION 和 METH_CALL_UNBOUND

新的标志 `METH_PASS_FUNCTION` 和 `METH_CALL_UNBOUND` 旨在支持以前使用自定义 `tp_call` 的情况。它减少了 `Python/ceval.c` 中调用对象的特殊快速路径数量:与其单独处理 Python 函数、内置函数和方法描述符,不如只有一个检查。

`tp_call` 的签名本质上是 `PyBaseFunctionObject.m_ml.ml_meth` 的签名,带有 `METH_VARARGS | METH_KEYWORDS | METH_PASS_FUNCTION | METH_CALL_UNBOUND` 标志(唯一区别是添加了一个 `self` 参数)。因此,很容易将现有的 `tp_call` 插槽更改为使用 `base_function` 实现。

在 C 函数需要访问函数中的额外元数据(如 `__parent__`)的情况下,使用 `METH_PASS_FUNCTION` 而不使用 `METH_CALL_UNBOUND` 也是有意义的。例如,这对于支持 PEP 573 是必需的。将现有方法转换为使用 `METH_PASS_FUNCTION` 非常简单:只需要为 C 函数添加一个额外的参数。

向后兼容性

在设计本 PEP 时,我们非常注意不要过多地破坏向后兼容性。大多数潜在的不兼容更改是 CPython 实现细节的更改,这些细节在其他 Python 解释器中本就不同。特别是,在 PyPy 上正确运行的 Python 代码很可能在此 PEP 中继续运行。

标准类和函数,如 `staticmethod`、`functools.partial` 或 `operator.methodcaller`,根本不需要更改。

对 types 和 inspect 的更改

对 `types` 和 `inspect` 的拟议更改旨在最大程度地减少行为变更。但是,某些内容不可避免地会发生变化,这可能导致使用 `types` 或 `inspect` 的代码中断。例如,在 Python 标准库中,`doctest` 模块由于此原因需要进行更改。

此外,处理各种类型函数的工具将需要处理新的函数层次结构以及自定义函数类的可能性。

Python 函数

对于 Python 函数,基本上没有变化。以前存在的属性仍然存在,并且 Python 函数可以像以前一样初始化、调用和转换为方法。

为向后兼容性保留了 `function` 这个名称。虽然将其更改为更具体的名称(如 `python_function`)可能是有意义的,但这需要大量烦人的文档和测试套件更改。

模块的内置函数

对于内置函数,也没有变化。我们保留了此类函数不绑定为方法的旧行为。这是因为 `__self__` 被设置为模块。

内置的绑定和未绑定方法

内置绑定和未绑定方法的类型将发生变化。但是,这不会影响调用此类方法,因为 `base_function.__call__` 中的协议(特别是对 `__objclass__` 和 self slicing 的处理)经过专门设计,以确保向后兼容性。所有以前存在的属性(如 `__objclass__` 和 `__self__`)仍然存在。

新属性

一些对象会获得新的特殊双下划线属性。例如,新属性 `__parent__` 会出现在所有内置函数上,并且所有方法都会获得 `__func__` 属性。`__self__` 现在是 Python 函数的特殊只读属性这一事实在 [4] 中引起了麻烦。总的来说,我们预计不会有太多东西会中断。

method_descriptor 和 PyDescr_NewMethod

应弃用 `method_descriptor` 类和构造函数 `PyDescr_NewMethod`。CPython 本身不再使用它们,但它们仍然得到支持。

两阶段实现

**待办**:本节是可选的。如果接受本 PEP,应决定是否应用此两阶段实现。

如上所述,对 types 和 inspect 的更改 会中断一些现有代码。为了进一步最大程度地减少中断,本 PEP 可以分两个阶段实施。

第一阶段:保留现有类,但添加基类

最初,实现 `base_function` 类,并将其用作通用基类,但否则保留现有类(但不是它们的实现)。

在此提案中,类层次结构将变为

                      object
                         |
                         |
                  base_function
                 /       |     \
                /        |      \
               /         |       \
      cfunction          |     defined_function
       |     |           |         \
       |     |      bound_method    \
       |     |                       \
       |  method_descriptor       function
       |
builtin_function_or_method

叶类 `builtin_function_or_method`、`method_descriptor`、`bound_method` 和 `function` 对应于现有类(`method` 重命名为 `bound_method`)。

模块中自动创建的函数将成为 `builtin_function_or_method` 的实例。扩展类型的未绑定方法将成为 `method_descriptor` 的实例。

`method_descriptor` 类是 `cfunction` 的副本,只是 `__get__` 返回 `builtin_function_or_method` 而不是 `bound_method`。

`method_descriptor` 类具有与 `bound_method` 相同的 C 结构,但它继承自 `cfunction`。`__func__` 属性不是必需的:仅在绑定 `method_descriptor` 时定义。

我们保留 `inspect` 函数的实现。由于这一点以及保留现有类,因此对于执行类型检查的代码,向后兼容性得到了保证。

由于显示实际的 `DeprecationWarning` 会影响大量运行正常的代码,因此任何弃用仅会出现在文档中。另一个原因是很难显示调用 `isinstance(x, t)` 的警告(但可以使用 `__instancecheck__` 技巧来完成),而对于 `type(x) is t` 则不可能。

第二阶段

第二阶段是本 PEP 其余部分实际描述的内容。在实现方面,与第一阶段相比,这将是一个相对较小的更改。

参考实现

本 PEP 的大部分内容已在 CPython 中实现,位于 https://github.com/jdemeyer/cpython/tree/pep575

有四个步骤,对应于该分支上的提交。每一步之后,CPython 都处于基本可用的状态。

  1. 添加 `base_function` 类,并使其成为 `cfunction` 的子类。这是迄今为止最大的一步,因为完整的 `__call__` 协议在此步骤中实现。
  2. 将 `method` 重命名为 `bound_method`,并使其成为 `base_function` 的子类。将扩展类型的未绑定方法更改为 `cfunction` 的实例,以便扩展类型的绑定方法也成为 `bound_method` 的实例。
  3. 实现 `defined_function` 和 `function`。
  4. Python 其他部分的更改,例如标准库和测试套件。

附录:当前情况

注意:本节在 PEP 的草稿期间更有用,因此一旦 PEP 被接受,可以随时删除。

供参考,我们详细描述了 CPython 3.7 中相关的现有类。

所涉及的每个类都是一个“孤儿”类(没有非平凡的子类或超类)。

builtin_function_or_method:内置函数和绑定方法

这些是 PyCFunction_Type 类型,结构为 PyCFunctionObject

typedef struct {
    PyObject_HEAD
    PyMethodDef *m_ml; /* Description of the C function to call */
    PyObject    *m_self; /* Passed as 'self' arg to the C func, can be NULL */
    PyObject    *m_module; /* The __module__ attribute, can be anything */
    PyObject    *m_weakreflist; /* List of weak references */
} PyCFunctionObject;

struct PyMethodDef {
    const char  *ml_name;   /* The name of the built-in function/method */
    PyCFunction ml_meth;    /* The C function that implements it */
    int         ml_flags;   /* Combination of METH_xxx flags, which mostly
                               describe the args expected by the C func */
    const char  *ml_doc;    /* The __doc__ attribute, or NULL */
};

其中 PyCFunction 是一个 C 函数指针(有各种形式,最基本的形式接收两个参数,分别是 self*args)。

此类既用于函数,也用于绑定方法:对于方法,m_self 插槽指向对象

>>> dict(foo=42).get
<built-in method get of dict object at 0x...>
>>> dict(foo=42).get.__self__
{'foo': 42}

在某些情况下,一个函数被认为是定义它的模块的“方法”

>>> import os
>>> os.kill
<built-in function kill>
>>> os.kill.__self__
<module 'posix' (built-in)>

method_descriptor:内置未绑定方法

这些是 PyMethodDescr_Type 类型,结构为 PyMethodDescrObject

typedef struct {
    PyDescrObject d_common;
    PyMethodDef *d_method;
} PyMethodDescrObject;

typedef struct {
    PyObject_HEAD
    PyTypeObject *d_type;
    PyObject *d_name;
    PyObject *d_qualname;
} PyDescrObject;

function:Python 函数

这些是 PyFunction_Type 类型,结构为 PyFunctionObject

typedef struct {
    PyObject_HEAD
    PyObject *func_code;        /* A code object, the __code__ attribute */
    PyObject *func_globals;     /* A dictionary (other mappings won't do) */
    PyObject *func_defaults;    /* NULL or a tuple */
    PyObject *func_kwdefaults;  /* NULL or a dict */
    PyObject *func_closure;     /* NULL or a tuple of cell objects */
    PyObject *func_doc;         /* The __doc__ attribute, can be anything */
    PyObject *func_name;        /* The __name__ attribute, a string object */
    PyObject *func_dict;        /* The __dict__ attribute, a dict or NULL */
    PyObject *func_weakreflist; /* List of weak references */
    PyObject *func_module;      /* The __module__ attribute, can be anything */
    PyObject *func_annotations; /* Annotations, a dict or NULL */
    PyObject *func_qualname;    /* The qualified name */

    /* Invariant:
     *     func_closure contains the bindings for func_code->co_freevars, so
     *     PyTuple_Size(func_closure) == PyCode_GetNumFree(func_code)
     *     (func_closure may be NULL if PyCode_GetNumFree(func_code) == 0).
     */
} PyFunctionObject;

在 Python 3 中,没有“未绑定方法”类:未绑定方法只是一个普通函数。

method:Python 绑定方法

这些是 PyMethod_Type 类型,结构为 PyMethodObject

typedef struct {
    PyObject_HEAD
    PyObject *im_func;   /* The callable object implementing the method */
    PyObject *im_self;   /* The instance it is bound to */
    PyObject *im_weakreflist; /* List of weak references */
} PyMethodObject;

参考资料


来源:https://github.com/python/peps/blob/main/peps/pep-0575.rst

最后修改:2025-02-01 08:55:40 GMT