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 需要(例如在PySequence_GetSlice
、PySequence_SetSlice
和PySequence_DelSlice
中)一个Py_ssize_t
值时,可以将此整数适当地转换为Py_ssize_t
值。
规范
nb_index
槽位将具有以下签名PyObject *index_func (PyObject *self)
返回的对象必须是 Python
IntType
或 PythonLongType
。如果发生错误,则应返回 NULL,并设置相应的错误。__index__
特殊方法将具有以下签名def __index__(self): return obj
其中 obj 必须是 int 或 long。
- 将添加 3 个新的抽象 C-API 函数
- 第一个检查对象是否支持索引槽位,以及它是否已填充。
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,则返回值将被裁剪到
PY_SSIZE_T_MAX
或PY_SSIZE_T_MIN
,具体取决于 obj 的nb_index
槽位返回正整数还是负整数。如果 exc 不为 NULL,则它是将被设置为替换PyExc_OverflowError
的错误对象,当 Python 整数或长整数转换为Py_ssize_t
时,会引发该错误。
- 第一个检查对象是否支持索引槽位,以及它是否已填充。
- 将添加一个新的
operator.index(obj)
函数,该函数将调用等效于obj.__index__()
的内容,如果 obj 没有实现特殊方法,则会引发错误。
实施计划
- 在
object.h
中添加nb_index
槽位,并修改typeobject.c
以创建__index__
方法 - 将
ceval.c
中的ISINT
宏更改为ISINDEX
,并修改它以适应定义了索引槽位的对象。 - 更改
_PyEval_SliceIndex
函数以适应定义了索引槽位的对象。 - 更改所有内置对象(例如列表),这些对象使用
as_mapping
槽位进行下标访问,并使用对整数的特殊检查来检查槽位。 - 将
nb_index
槽位添加到整数和长整数(它们只返回自身) - 添加
PyNumber_Index
C-API 以从具有nb_index
槽位的任何 Python 对象返回一个整数。 - 添加
operator.index(x)
函数。 - 更改
arrayobject.c
和mmapmodule.c
以使用新的 C-API 进行其下标访问和其他需求。 - 添加单元测试
讨论问题
速度
实现不应减慢 Python 的速度,因为用作索引的整数和长整数将在相同数量的指令中完成。唯一变化的是,过去会生成错误的内容现在将被接受。
为什么不使用nb_int
,它已经存在了?
nb_int
方法用于强制转换,因此其含义与这里请求的内容从根本上不同。本 PEP 提出了一种方法,用于已经可以被认为是整数的对象在 Python 需要整数时向其传达该信息。使用nb_int
将是一件坏事的最重要例子是浮点数对象已经定义了nb_int
方法,但浮点数对象不应用作序列中的索引。
为什么名称为__index__
?
有人提出了关于__index__
名称的问题,因为槽位还有其他可能的解释。例如,该槽位可以在 Python 内部需要整数的任何时候使用(例如在"mystring" * 3
中)。Guido 建议使用此名称,因为切片语法是拥有此类槽位的最主要原因,最终没有出现更好的名称。有关建议名称(例如“__discrete__
”和“__ordinal__
”)的示例,请参阅讨论线程[1]。
为什么从nb_index
返回PyObject *
?
最初,Py_ssize_t
被选为nb_index
槽位的返回类型。但是,这导致无法跟踪和区分溢出和下溢错误,而无需使用丑陋且脆弱的技巧。由于nb_index
槽位至少以 3 种不同的方式用于 Python 核心(获取整数、获取切片终点和获取序列索引),因此需要相当大的灵活性来处理所有这些情况。能够灵活处理所有用例非常重要。例如,最初返回nb_index
的Py_ssize_t
的实现导致发现,在具有 >=2GB RAM 的 32 位机器上,s = 'x' * (2**100)
有效,但len(s)
被裁剪为 2147483647。提出了几种修复方法,但最终决定nb_index
需要返回一个类似于nb_int
和nb_long
槽位的 Python 对象,以便正确处理溢出。
为什么__index__
不能返回任何具有nb_index
方法的对象?
这将以许多不同的方式允许无限递归,这些方式不容易检查。此限制类似于__nonzero__
返回 int 或 bool 的要求。
参考实现
作为补丁 1436368 提交到 SourceForge。
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0357.rst