我在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表现不同?
答案 0 :(得分:38)
因为xrange
没有返回生成器。它返回xrange object。
>>> 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
个对象实现了完整的sequence interface:
>>> import collections
>>> isinstance(xrange(10), collections.Sequence)
True
他们只是在不浪费大量内存的情况下做到这一点。
另请注意,在Python 3中,range
返回的range
对象具有所有相同的属性。
答案 1 :(得分:17)
因为调用xrange
生成的xrange()
对象指定了__iter__
,每次迭代时都会提供自身的唯一版本(实际上是一个单独的rangeiterator
对象)。
>>> x = xrange(3)
>>> type(x)
<type 'xrange'>
>>> i = x.__iter__()
>>> type(i)
<type 'rangeiterator'>
答案 2 :(得分:2)
如果你所知道的是它是一个迭代器,那么在 general 中你必须假设你只能迭代一次。这并不意味着每个迭代器只能被消耗一次,只是每个迭代器都可以消耗至少一次。明显的例子是列表和其他序列支持这个接口。
正如senderle和Amber所解释的那样,通过调用xrange
获得的特定迭代器恰好被实现,以便您可以多次迭代它们。
一般迭代器的想法允许迭代器在迭代后可能会耗尽。这是因为许多迭代器(例如生成器,文件遍历等)难以实现,或者消耗更多内存或运行速度慢得多,如果它们必须支持任意多次遍历,并且通常这种功能甚至不会用过的。因此,如果迭代器必须支持任意多次遍历,那么这些东西可能不会是迭代器。
简而言之,如果您正在编写在任意未知迭代器上运行的代码,您可以假设它只能遍历一次,如果某人为您提供了支持 more的对象,则无关紧要比你需要的功能。如果您知道有关迭代器的一些其他信息(例如它也是一个序列,或者甚至是它是一个xrange对象),那么您可以编写代码以便在需要时使用它。