29

我从Most pythonic way of counting matching elements in something iterable 中遇到了这段代码

r = xrange(1, 10)
print sum(1 for v in r if v % 2 == 0) # 4
print sum(1 for v in r if v % 3 == 0) # 3

r 被迭代一次。然后再次迭代。我想如果一个迭代器被消耗一次,那么它就结束了,它不应该再次迭代。

生成器表达式只能迭代一次:

r = (7 * i for i in xrange(1, 10))
print sum(1 for v in r if v % 2 == 0) # 4
print sum(1 for v in r if v % 3 == 0) # 0

也枚举(L):

r = enumerate(mylist)

和文件对象:

f = open(myfilename, 'r')

为什么 xrange 的行为不同?

4

3 回答 3

38

因为xrange不返回生成器。它返回一个xrange 对象

>>> type(xrange(10))
<type 'xrange'>

除了重复迭代之外,xrange对象还支持生成器不支持的其他事情——比如索引:

>>> xrange(10)[5]
5

它们也有一个长度:

>>> len(xrange(10))
10

它们可以颠倒过来:

>>> list(reversed(xrange(10)))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

简而言之,xrange对象实现了完整的序列 接口

>>> import collections
>>> isinstance(xrange(10), collections.Sequence)
True

他们只是在不占用大量内存的情况下这样做。

另请注意,在 Python 3 中,range返回的对象range具有所有相同的属性。

于 2012-05-27T18:31:16.487 回答
17

因为xrange调用生成的对象xrange()指定了一个在每次迭代时__iter__提供自身唯一版本(实际上是一个单独的对象)的对象。rangeiterator

>>> x = xrange(3)
>>> type(x)
<type 'xrange'>
>>> i = x.__iter__()
>>> type(i)
<type 'rangeiterator'>
于 2012-05-27T18:29:41.043 回答
2

如果您所知道的只是它是一个迭代器,那么通常您必须假设您只能迭代它一次。这并不意味着每个迭代器只能被使用一次,只是每个迭代器至少可以被使用一次。一个明显的例子是列表和其他序列支持这个接口。

正如 senderle 和 Amber 所解释的,您通过调用获得的特定xrange迭代器恰好被实现,以便您可以多次迭代它们。

一般的迭代器思想允许迭代器在被迭代后可能会被耗尽。这是因为许多迭代器(例如生成器、文件遍历等)如果必须支持任意多的遍历,将难以实现,或者消耗更多的内存或运行得更慢,而且通常这个功能甚至不会用过的。所以如果迭代器必须支持任意多的遍历,那么这些东西可能就不是迭代器了。

长话短说,如果您正在编写在任意未知迭代器上运行的代码,您会假设它只能被遍历一次,并且如果有人给您一个支持超出您需要的功能的对象,这并不重要。如果您知道有关迭代器的一些其他信息(例如它也是一个序列,或者甚至与它是一个 xrange 对象一样多),那么您可以根据需要编写代码来使用它。

于 2012-06-29T01:43:12.157 回答