由于范围对象按需生成值,这是否意味着每次对范围进行索引时,迭代协议都会被调用直到该索引?
我的意思是,在以下情况下:
>>> R = range(1,11)
>>> print(R[5])
6
由于 R[5]
没有存储在内存中,那么每次都会创建一个新的迭代器来计算吗?如果不是这样,那么如何对范围对象进行索引?
由于范围对象按需生成值,这是否意味着每次对范围进行索引时,迭代协议都会被调用直到该索引?
我的意思是,在以下情况下:
>>> R = range(1,11)
>>> print(R[5])
6
由于 R[5]
没有存储在内存中,那么每次都会创建一个新的迭代器来计算吗?如果不是这样,那么如何对范围对象进行索引?
这里不创建迭代器,也不进行迭代。 range
对象的实现方式是使Python在常量时间内按需计算R[5]
的值。1
如果索引i
不是负数,则计算简化为:
i * step + start
在你的代码R [5]
的情况下,这将是5 * 1 + 1
,即6。
如果索引i
为负数,则首先将R
的长度添加到i
中,然后再像之前一样计算值:
(i + len(R)) * step + start
当你写下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
会检查传递给它的参数(R
和5
),以确定是请求单个索引还是切片。整数5
表示只需要一个单一的索引,因此该函数将计算值的委托交给compute_range_item
。该函数执行计算,返回整数6,正如本回答的第一部分所概述的那样。
1我假设您正在使用CPython:其他Python实现可能会以不同的方式实现range
对象。
2如果您调用len(R)
,则可以看到在mp_length
中调用的内部函数,该函数用于计算R
的长度(参见为什么在Python 3中“1000000000000000 in range(1000000000000001)”如此快?)。
不是的。
然而,range
支持迭代协议和索引(通过getitem)。
__getitem__
和__len__
,以及序列协议。 - lvc