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

Python 增强提案

PEP 357 – 允许任何对象用于切片

作者:
Travis Oliphant <oliphant at ee.byu.edu>
状态:
最终版
类型:
标准跟踪
创建日期:
2006年2月9日
Python 版本:
2.5
发布历史:


目录

摘要

本 PEP 提议在 PyNumberMethods 中添加一个 nb_index 槽位和一个 __index__ 特殊方法,以便在 Python 明确需要整数时(例如在切片语法中,该槽位也因此得名)可以使用任意对象。

基本原理

目前,整数和长整数在切片中扮演着特殊角色,因为它们是切片语法中唯一允许的对象。换句话说,如果 X 是实现序列协议的对象,那么 X[obj1:obj2] 仅在 obj1obj2 都是整数或长整数时才有效。没有办法让 obj1obj2 告诉 Python 它们可以合理地用作序列的索引。这是一个不必要的限制。

例如,在 NumPy 中,有 8 种不同的整数标量,对应于 8、16、32 和 64 位的无符号和有符号整数。这些类型对象可以在 Python 期望真实整数的许多地方合理地用作整数,但由于内存布局不兼容,它们无法继承 Python 整数类型。应该有某种方法能够告诉 Python 一个对象可以像整数一样工作。

为此目的使用 nb_int(以及 __int__ 特殊方法)是不可能的,因为该方法用于将对象强制转换为整数。允许每个可以强制转换为整数的对象在 Python 期望真实整数的所有地方都用作整数是不合适的。例如,如果 __int__ 用于在切片中将对象转换为整数,那么浮点对象将被允许在切片中使用,并且 x[3.2:5.8] 将不会像预期那样引发错误。

提案

PyNumberMethods 中添加一个 nb_index 槽位,以及一个对应的 __index__ 特殊方法。对象可以定义一个函数放置在 nb_index 槽位中,该函数返回一个 Python 整数(int 或 long)。然后,每当 Python 需要一个 Py_ssize_t 值时,例如在 PySequence_GetSlicePySequence_SetSlicePySequence_DelSlice 中,该整数可以被适当地转换为该值。

规范

  1. nb_index 槽位将具有以下签名
    PyObject *index_func (PyObject *self)
    

    返回的对象必须是 Python IntType 或 Python LongType。发生错误时应返回 NULL 并设置适当的错误。

  2. __index__ 特殊方法将具有以下签名
    def __index__(self):
        return obj
    

    其中 obj 必须是 int 或 long。

  3. 将添加 3 个新的抽象 C-API 函数
    1. 第一个检查对象是否支持 index 槽位以及它是否已填充。
      int PyIndex_Check(obj)
      

      如果对象定义了 nb_index 槽位,则此函数将返回 true。

    2. 第二个是 nb_index 调用的简单包装器,如果调用不可用或未返回 int 或 long,则会引发 PyExc_TypeError。由于 PyIndex_CheckPyNumber_Index 调用内部执行,因此您可以直接调用它并管理任何错误,而不必首先检查兼容性。
      PyObject *PyNumber_Index (PyObject *obj)
      
    3. 第三个调用有助于处理从对象获取 Py_ssize_t 值以用于索引或其他需求的常见情况。
      Py_ssize_t PyNumber_AsSsize_t(PyObject *obj, PyObject *exc)
      

      如果 obj 的 nb_index 槽位可用,则该函数将调用它,然后将返回的 Python 整数转换为 Py_ssize_t 值。如果转换顺利,则返回该值。第二个参数允许控制如果从 nb_index 返回的整数无法放入 Py_ssize_t 值中会发生什么。

      如果 exc 为 NULL,则根据 obj 的 nb_index 槽位返回的是正整数还是负整数,返回的值将被截断为 PY_SSIZE_T_MAXPY_SSIZE_T_MIN。如果 exc 不为 NULL,则它是将 Python 整数或长整数转换为 Py_ssize_t 时引发的 PyExc_OverflowError 的替代错误对象。

  4. 将添加一个 operator.index(obj) 函数,该函数调用等同于 obj.__index__(),如果 obj 未实现该特殊方法则会引发错误。

实施计划

  1. object.h 中添加 nb_index 槽位,并修改 typeobject.c 以创建 __index__ 方法
  2. ceval.c 中的 ISINT 宏更改为 ISINDEX,并对其进行修改以适应定义了 index 槽位的对象。
  3. 修改 _PyEval_SliceIndex 函数以适应定义了 index 槽位的对象。
  4. 更改所有使用 as_mapping 槽位进行下标访问的内置对象(例如列表),并使用特殊检查来检查整数是否也具有该槽位。
  5. 为整数和长整数添加 nb_index 槽位(它们只需返回自身)
  6. 添加 PyNumber_Index C-API,用于从任何具有 nb_index 槽位的 Python 对象返回一个整数。
  7. 添加 operator.index(x) 函数。
  8. 修改 arrayobject.cmmapmodule.c,使其使用新的 C-API 进行下标和其他需求。
  9. 添加单元测试

讨论问题

速度

实施不应降低 Python 的速度,因为用作索引的整数和长整数将以相同的指令数完成。唯一的改变是,以前会产生错误的情况现在将变为可接受。

为什么不使用已存在的 nb_int

nb_int 方法用于强制转换,因此其含义与此处要求的功能根本不同。本 PEP 提出了一种方法,用于将一个 可以 被认为是整数的对象在 Python 需要整数时将该信息传达给 Python。说明为什么使用 nb_int 会是一个坏主意的一个最大例子是,浮点对象已经定义了 nb_int 方法,但浮点对象 不应该 用作序列的索引。

为什么命名为 __index__

关于 __index__ 名称的某些问题被提出,因为该槽位可能有其他解释。例如,每当 Python 内部需要整数时(例如在 "mystring" * 3 中),都可以使用该槽位。该名称由 Guido 建议,因为切片语法是设置此类槽位的最大原因,最终也没有更好的名称出现。请参阅讨论串 [1],其中包含一些建议的名称,例如 “__discrete__” 和 “__ordinal__”。

为什么 nb_index 返回 PyObject *

最初,Py_ssize_t 被选作 nb_index 槽位的返回类型。然而,这导致了在没有丑陋且脆弱的 HACK 情况下无法跟踪和区分溢出和下溢错误。由于 nb_index 槽位在 Python 核心中至少以 3 种不同的方式使用(获取整数、获取切片端点和获取序列索引),因此需要相当大的灵活性来处理所有这些情况。拥有处理所有用例所需的灵活性至关重要。例如,最初将 nb_index 返回 Py_ssize_t 的实现导致发现在具有 >=2GB RAM 的 32 位机器上,s = 'x' * (2**100) 可以工作,但 len(s) 被截断为 2147483647。提出了几种修复方法,但最终决定 nb_index 需要返回一个 Python 对象,类似于 nb_intnb_long 槽位,以便正确处理溢出。

为什么 __index__ 不能返回带有 nb_index 方法的任何对象?

这将允许以许多不同方式进行无限递归,而这些方式不容易检查。此限制类似于要求 __nonzero__ 返回 int 或 bool。

参考实现

作为补丁 1436368 提交至 SourceForge。

参考资料


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

上次修改:2025-02-01 08:55:40 GMT