索引范围对象时是否使用迭代协议?

Is the iteration protocol used when indexing a range object?

由于范围对象按需生成值,这是否意味着只要范围被索引,迭代协议就会被调用到该索引?

我的意思是什么时候:

>>> R = range(1,11)
>>> print(R[5])
6

既然R[5]没有存储在内存中,是不是每次都通过创建一个新的迭代器来计算?如果不是,如何索引范围对象?

没有。不是。

但是range同时支持迭代协议和索引(通过getitem

这里没有创建迭代器,也没有迭代发生。 range 对象的实现使得 Python 在恒定时间内按需计算 R[5] 的值。1

如果索引 i 不是负数,则计算归结为:

i * step + start

因此,对于您的代码 R[5],这将是 5*1 + 1,即 6。

如果索引i为负数,则先将R的长度加到i上,然后再计算值:

(i + len(R)) * step + start

Python-内部

当您编写 R[5] 时,此 Python 语法最终会转换为对 PyObject_GetItem 的调用,它会检查对象 R 以了解它应该如何继续在索引 5 找到项目。

PyObject_GetItem首先检查这个结构的tp_as_mapping slot of the range type. This is not null; it holds a reference to a struct called range_as_mapping. PyObject_GetItem then checks to see what is in the mp_subscript field

static PyMappingMethods range_as_mapping = {
        (lenfunc)range_length,       /* mp_length */
        (binaryfunc)range_subscript, /* mp_subscript */
        (objobjargproc)0,            /* mp_ass_subscript */
};

正如您在上面的代码片段中看到的,它发现 range_subscript 函数占据了 mp_subscript 字段。2

现在 range_subscript 检查传递给它的参数(R5)来决定请求的是单个索引还是切片。整数 5 意味着只需要一个索引,因此该函数将值的计算委托给 compute_range_item。如本答案第一部分所述,此函数执行 return 整数 6 的计算。


1 我假设您使用的是 CPython:其他 Python 实现可能会以不同方式实现 range 对象。

2 如果你要调用 len(R),你可以看到 mp_length 中的内部函数被调用来计算 [=19 的长度=](参见)。