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] 仅在 obj1 和 obj2 都是整数或长整数时才有效。没有办法让 obj1 和 obj2 告诉 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_GetSlice、PySequence_SetSlice 和 PySequence_DelSlice 中,该整数可以被适当地转换为该值。
规范
nb_index槽位将具有以下签名PyObject *index_func (PyObject *self)
返回的对象必须是 Python
IntType或 PythonLongType。发生错误时应返回 NULL 并设置适当的错误。__index__特殊方法将具有以下签名def __index__(self): return obj
其中 obj 必须是 int 或 long。
- 将添加 3 个新的抽象 C-API 函数
- 第一个检查对象是否支持 index 槽位以及它是否已填充。
int PyIndex_Check(obj)
如果对象定义了
nb_index槽位,则此函数将返回 true。 - 第二个是
nb_index调用的简单包装器,如果调用不可用或未返回 int 或 long,则会引发PyExc_TypeError。由于PyIndex_Check在PyNumber_Index调用内部执行,因此您可以直接调用它并管理任何错误,而不必首先检查兼容性。PyObject *PyNumber_Index (PyObject *obj)
- 第三个调用有助于处理从对象获取
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_MAX或PY_SSIZE_T_MIN。如果 exc 不为 NULL,则它是将 Python 整数或长整数转换为Py_ssize_t时引发的PyExc_OverflowError的替代错误对象。
- 第一个检查对象是否支持 index 槽位以及它是否已填充。
- 将添加一个
operator.index(obj)函数,该函数调用等同于obj.__index__(),如果 obj 未实现该特殊方法则会引发错误。
实施计划
- 在
object.h中添加nb_index槽位,并修改typeobject.c以创建__index__方法 - 将
ceval.c中的ISINT宏更改为ISINDEX,并对其进行修改以适应定义了 index 槽位的对象。 - 修改
_PyEval_SliceIndex函数以适应定义了 index 槽位的对象。 - 更改所有使用
as_mapping槽位进行下标访问的内置对象(例如列表),并使用特殊检查来检查整数是否也具有该槽位。 - 为整数和长整数添加
nb_index槽位(它们只需返回自身) - 添加
PyNumber_IndexC-API,用于从任何具有nb_index槽位的 Python 对象返回一个整数。 - 添加
operator.index(x)函数。 - 修改
arrayobject.c和mmapmodule.c,使其使用新的 C-API 进行下标和其他需求。 - 添加单元测试
讨论问题
速度
实施不应降低 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_int 和 nb_long 槽位,以便正确处理溢出。
为什么 __index__ 不能返回带有 nb_index 方法的任何对象?
这将允许以许多不同方式进行无限递归,而这些方式不容易检查。此限制类似于要求 __nonzero__ 返回 int 或 bool。
参考实现
作为补丁 1436368 提交至 SourceForge。
参考资料
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0357.rst