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

5

由于范围对象按需生成值,这是否意味着每次对范围进行索引时,迭代协议都会被调用直到该索引?

我的意思是,在以下情况下:

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

由于 R[5] 没有存储在内存中,那么每次都会创建一个新的迭代器来计算吗?如果不是这样,那么如何对范围对象进行索引?

2个回答

6

这里不创建迭代器,也不进行迭代。 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首先检查range类型的tp_as_mapping槽位。这不是null/null指针,而是保留着range_as_mapping结构体的引用。接下来,PyObject_GetItem检查此结构体的mp_subscript字段中存储的内容:

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

正如您在上面的代码片段中所看到的,它找到了占用mp_subscript字段的range_subscript函数。2 现在,range_subscript会检查传递给它的参数(R5),以确定是请求单个索引还是切片。整数5表示只需要一个单一的索引,因此该函数将计算值的委托交给compute_range_item。该函数执行计算,返回整数6,正如本回答的第一部分所概述的那样。

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

2如果您调用len(R),则可以看到在mp_length中调用的内部函数,该函数用于计算R的长度(参见为什么在Python 3中“1000000000000000 in range(1000000000000001)”如此快?)。


除了这些 C 级别的函数之外,值得一提的是 Python 级别的 __getitem____len__,以及序列协议。 - lvc

-1

不是的。

然而,range支持迭代协议和索引(通过getitem)。


1
嗨,谢谢您的回复!我查看了getitem的文档,但是不确定是否理解正确。如果它等同于在对象中使用self[key],那么在虚拟序列中仍然存在知道值的问题(因为它不存储在内存中),不是吗? - masiewpao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接