Python中的迭代器和range

range是迭代器吗

一直以为range是迭代器,所以很自然地用next方法试图迭代它的时候,居然报了下面的错误:

1
2
3
4
5
6
7
8
#range不是迭代器的事实
x = range(3)
print(next(x))
-------------------
Traceback (most recent call last):
File "pythonscripts/iterator_and_range.py", line 8, in <module>
print(next(x))
TypeError: 'range' object is not an iterator

那么这就有意思了,既然range不是迭代器,那么它究竟是什么?

迭代器的特性

我们先来复习一下迭代器的定义, 迭代器是指实现了迭代协议的对象,迭代协议是指实现了iter方法并返回一个实现了next方法的迭代器对象,并通过StopIterator异常标识迭代完成。

使用iter函数可以从任何可迭代对象中获取一个迭代器

1
2
3
4
5
6
7
8
s = iter([1,2,3])
print(s)
print(next(s))
print(1 in s)
-----------------
<list_iterator object at 0x7f7694fc4a20>
1
False

迭代器有个特点,即每用完一个元素即消耗掉该元素,不会保留在迭代器中,也就是说,是一次性的。

迭代器上使用iter会得到相同的对象

1
2
3
4
5
6
7
8
s = iter([1,2,3])
print(iter(s) is s)
print(id(iter(s)))
print(id(s))
------------------
True
140143916509728
140143916509728

利用这个特性,我们可以实现一些特殊的操作:

1
2
3
4
s = iter([1,2,3,4])
list(zip(s,s))
------------------
[(1, 2), (3, 4)]

range有上述的特性吗

1
2
3
4
5
6
s = iter(range(5))
print(s)
print(next(s))
------------------
<range_iterator object at 0x7fef611b9a20>
0

从这里可以看出iter可以用在range上,并返回了一个迭代器。

1
2
3
4
5
6
7
8
x = range(5)
print(1 in x)
print(2 in x)
print(1 in x)
-------------
True
True
True

这里可以看出,range并不是一次性的,而是可以重复地使用,不符合迭代器一次性的特性

而且,迭代器(生成器)并不支持长度,而range支持len方法:

1
2
3
4
5
6
7
x = iter([1,2,3])
print(len(x))
-------------
Traceback (most recent call last):
File "pythonscripts/iterator_and_range.py", line 11, in <module>
print(len(x))
TypeError: object of type 'list_iterator' has no len()

也不可以被索引:

1
2
3
4
5
6
7
x = iter([1,2,3])
print(x[0])
------------
Traceback (most recent call last):
File "pythonscripts/iterator_and_range.py", line 11, in <module>
print(x[0])
TypeError: 'list_iterator' object is not subscriptable

综上,range并不是迭代器,而且跟迭代器有着明显的区别,只是range是一种可迭代的对象,表面上看起来像迭代器而已。

那么,range究竟是什么

range实现是c语言写的,源码可以在安装路径下找到,这里我们看一下help方法给我们的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
Help on class range in module builtins:
class range(object)
| range(stop) -> range object
| range(start, stop[, step]) -> range object
|
| Return an object that produces a sequence of integers from start (inclusive)
| to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.
| start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.
| These are exactly the valid indices for a list of 4 elements.
| When step is given, it specifies the increment (or decrement).
|
| Methods defined here:
|
| __contains__(self, key, /)
| Return key in self.
|
| __eq__(self, value, /)
| Return self==value.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getitem__(self, key, /)
| Return self[key].
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
|
| __iter__(self, /)
| Implement iter(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __len__(self, /)
| Return len(self).
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| __reduce__(...)
| helper for pickle
| __repr__(self, /)
| Return repr(self).
|
| __reversed__(...)
| Return a reverse iterator.
|
| count(...)
| rangeobject.count(value) -> integer -- return number of occurrences of value
|
| index(...)
| rangeobject.index(value, [start, [stop]]) -> integer -- return index of value.
| Raise ValueError if the value is not present.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| start
|
| step
| stop

 可以看出range的迭代是通过iter协议实现的,像len等对应的方法也是通过len等方式实现的,也就是说range其实是一种类似迭代器的鸭子类型,而并非真正的迭代器!