Python for循环和迭代器行为

我想更多地了解iterators ,所以请纠正我,如果我错了。

迭代器是一个对象,它有一个指向下一个对象的指针,并被读作缓冲区或stream(即链表)。 他们是特别有效的,因为他们所做的只是告诉你什么是下一步引用,而不是使用索引。

但是我仍然不明白为什么会发生以下行为:

 In [1]: iter = (i for i in range(5)) In [2]: for _ in iter: ....: print _ ....: 0 1 2 3 4 In [3]: for _ in iter: ....: print _ ....: In [4]: 

经过迭代器( In [2] )的第一次循环之后,就好像它已被消耗并留空,所以第二个循环( In [3] )不会打印任何东西。

但是我从来没有给itervariables赋值。

for循环的底层真正发生了什么?

你的怀疑是正确的:迭代器已被消耗。

实际上,你的迭代器是一个生成器 ,它是一个只能迭代一次的对象。

 type((i for i in range(5))) # says it's type generator def another_generator(): yield 1 # the yield expression makes it a generator, not a function type(another_generator()) # also a generator 

他们高效的原因与告诉你下一步“通过参考”无关。 它们是有效率的,因为它们只根据要求产生下一个项目; 所有的项目都不是一次生成的。 事实上,你可以有一个无限的发电机:

 def my_gen(): while True: yield 1 # again: yield means it is a generator, not a function for _ in my_gen(): print(_) # hit ctl+c to stop this infinite loop! 

其他一些更正,以帮助提高你的理解:

  • 生成器不是一个指针,不像其他语言中熟悉的那样,它的行为不像指针。
  • 与其他语言的区别之一:如上所述,生成器的每个结果都是即时生成的。 下一个结果直到请求时才产生。
  • in的关键字组合接受一个可迭代对象作为其第二个参数。
  • 可迭代的对象可以是一个生成器,就像在你的例子中那样,但是它也可以是任何其他可迭代的对象,比如listdict或者str对象(string),或者是一个用户定义的types,function。
  • iter函数被应用到对象来获得一个迭代器(顺便说一下:不要在Python中使用iter作为variables名,就像你所做的那样 – 它是关键字之一)。 实际上,更确切地说,调用了对象的__iter__方法 (大多数情况下,它的所有iter函数都是这样做的; __iter__是Python所谓的“魔术方法”之一)。
  • 如果对__iter__的调用成功,则next()函数将循环应用于可迭代对象,并将为for提供的第一个variables分配给next()函数的结果。 (记住:可迭代的对象可能是一个生成器,或者一个容器对象的迭代器,或者任何其他可迭代对象。)实际上,更确切地说:它调用迭代器对象的__next__方法,这是另一种“神奇的方法”。
  • next()引发StopIterationexception(通常在迭代器调用next()时没有其他对象产生)时, for循环结束。

你可以用这种方式“手动”实现一个for循环(可能不完美,但足够接近):

 try: temp = iterable.__iter__() except AttributeError(): raise TypeError("'{}' object is not iterable".format(type(iterable).__name__)) else: while True: try: _ = temp.__next__() except StopIteration: break except AttributeError: raise TypeError("iter() returned non-iterator of type '{}'".format(type(temp).__name__)) # this is the "body" of the for loop continue 

上面和你的示例代码几乎没有区别。

实际上, for循环中更有趣的部分不是for ,而是in 。 使用in本身会产生与in不同的效果,但是理解它的参数in做什么是非常有用的,因为for in实现非常类似的行为。

  • in使用时, in关键字首先调用对象的__contains__方法 ,这是另一种“神奇的方法”(请注意,在使用in时跳过此步骤)。 单独使用一个容器,你可以做这样的事情:

     1 in [1, 2, 3] # True 'He' in 'Hello' # True 3 in range(10) # True 'eH' in 'Hello'[::-1] # True 
  • 如果可迭代对象不是容器(即它没有__contains__方法),则在下一次尝试调用对象的__iter__方法。 如前所述: __iter__方法返回Python中已知的迭代器 。 基本上,迭代器是一个对象,您可以使用内置的通用函数next() on。 生成器只是一种迭代器。

  • 如果对__iter__的调用成功,则in关键字会一次又一次地将next()函数应用于可迭代对象。 (记住:可迭代对象可以是一个生成器,也可以是一个容器对象的迭代器,或任何其他可迭代对象。)实际上,更确切地说:它调用迭代器对象的__next__方法)。
  • 如果对象没有返回迭代器的__iter__方法,则使用对象的__getitem__方法返回到旧式迭代协议。
  • 如果所有上述尝试失败,您将得到一个TypeErrorexception 。

如果您希望创build自己的对象types来迭代(即,您可以使用inin ,on),那么了解生成器中使用的yield关键字(如上所述)是非常有用的。

 class MyIterable(): def __iter__(self): yield 1 m = MyIterable() for _ in m: print(_) # 1 1 in m # True 

yield的存在将函数或方法转换为一个生成器,而不是一个常规的函数/方法。 如果您使用生成器,则不需要__next__方法(它会自动带来__next__ )。

如果你想创build自己的容器对象types(也就是说,你可以自己使用它,而不是in ),你只需要__contains__方法。

 class MyUselessContainer(): def __contains__(self, obj): return True m = MyUselessContainer() 1 in m # True 'Foo' in m # True TypeError in m # True None in m # True 

1请注意,要成为迭代器,对象必须实现迭代器协议 。 这只意味着__next____iter__方法都必须正确实现(发生器免费提供这个function,所以在使用时不需要担心)。 另请注意, Python 2中的___next__方法实际上是next (不带下划线) 。

2请参阅此答案以创build可迭代类的不同方法。

For循环主要调用应用于(Python 3中的__next__ )的对象的next方法。

你可以简单地通过这样来模拟:

 iter = (i for i in range(5)) print(next(iter)) print(next(iter)) print(next(iter)) print(next(iter)) print(next(iter)) # this prints 1 2 3 4 

此时input对象中没有下一个元素。 所以这样做:

 print(next(iter)) 

将导致StopIterationexception抛出。 此时将停止。 迭代器可以是任何将响应next()函数的对象 ,并在没有更多元素时抛出exception。 它不一定是任何指针或引用(在C / C + +的意义上,在Python中没有这样的事情),链表等。

python中有一个迭代器协议,它定义了for语句如何与列表和字典以及其他可以循环的东西相联系。

这是在这里和这里的python文档。

迭代器协议的工作方式通常是以python生成器的forms。 只要我们有价值,我们就会产生一个价值,直到我们达到目的,然后我们提高StopIteration

那么让我们写我们自己的迭代器:

 def my_iter(): yield 1 yield 2 yield 3 raise StopIteration() for i in my_iter(): print i 

结果是:

 1 2 3 

有几件事需要注意。 my_iter是一个函数。 my_iter()返回一个迭代器。

如果我使用这样的迭代器来代替:

 j = my_iter() #j is the iterator that my_iter() returns for i in j: print i #this loop runs until the iterator is exhausted for i in j: print i #the iterator is exhausted so we never reach this line 

结果和上面一样。 当我们进入第二个循环的时候,iter已经耗尽了。

但是,这更简单些,更复杂的事情呢? 也许也许在一个循环中为什么不呢?

 def capital_iter(name): for x in name: yield x.upper() raise StopIteration() for y in capital_iter('bobert'): print y 

当它运行时,我们使用stringtypes的迭代器(内置于iter中 )。 这反过来又允许我们在它上面运行for循环,并在结束之前得出结果。

 B O B E R T 

所以现在这就引发了这个问题,那么迭代器中的yield之间会发生什么呢?

 j = capital_iter("bobert") print i.next() print i.next() print i.next() print("Hey there!") print i.next() print i.next() print i.next() print i.next() #Raises StopIteration 

答案是函数在等待next()的下一个调用的yield时被暂停。

 B O B Hey There! E R T Traceback (most recent call last): File "", line 13, in StopIteration 

一些关于iter()__getitem__类缺乏自己的__iter__方法的更多细节。


__iter__之前有__getitem__ 。 如果__getitem__使用0len(obj)-1 int s,那么iter()支持这些对象。 它将构造一个新的迭代器,该迭代器使用0...反复调用__getitem__ ,直到得到一个IndexError ,并将其转换为StopIteration

有关创build迭代器的不同方法的更多详细信息,请参阅此答案 。

概念1

所有的生成器都是迭代器,但所有的迭代器都不是生成器

概念2

迭代器是具有下一个(Python 2)或下一个 (Python 3)方法的对象。

概念3

引用wiki 生成器生成器函数允许你声明一个像迭代器一样的函数,也就是说它可以在for循环中使用。

在你的情况

 >>> it = (i for i in range(5)) >>> type(it) <type 'generator'> >>> callable(getattr(it, 'iter', None)) False >>> callable(getattr(it, 'next', None)) True 

摘自Python Practice Book :


5.迭代器和发生器

5.1。 迭代器

我们使用for语句来遍历一个列表。

 >>> for i in [1, 2, 3, 4]: ... print i, ... 1 2 3 4 

如果我们用一个string来使用它,它会遍历它的字符。

 >>> for c in "python": ... print c ... p y t h o n 

如果我们使用字典,它将循环使用它的键。

 >>> for k in {"x": 1, "y": 2}: ... print k ... y x 

如果我们将它与一个文件一起使用,它将循环遍历文件的各行。

 >>> for line in open("a.txt"): ... print line, ... first line second line 

所以有很多types的对象可以用for循环。 这些被称为可迭代对象。

有许多函数会消耗这些迭代。

 >>> ",".join(["a", "b", "c"]) 'a,b,c' >>> ",".join({"x": 1, "y": 2}) 'y,x' >>> list("python") ['p', 'y', 't', 'h', 'o', 'n'] >>> list({"x": 1, "y": 2}) ['y', 'x'] 

5.1.1。 迭代协议

内置函数iter需要一个可迭代对象并返回一个迭代器。

  >>> x = iter([1, 2, 3]) >>> x <listiterator object at 0x1004ca850> >>> x.next() 1 >>> x.next() 2 >>> x.next() 3 >>> x.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> 

的StopIteration

每次我们调用迭代器的下一个方法给我们下一个元素。 如果没有更多的元素,则会引发StopIteration。

迭代器被实现为类。 这是一个像内置的xrange函数一样工作的迭代器。

 class yrange: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() 

iter方法是使对象可迭代的原因。 在幕后,iter函数调用给定对象的iter方法。

iter的返回值是一个迭代器。 它应该有一个下一个方法,并提出StopIteration时,没有更多的元素。

让我们试试看:

 >>> y = yrange(3) >>> y.next() 0 >>> y.next() 1 >>> y.next() 2 >>> y.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 14, in next 

的StopIteration

许多内置函数接受迭代器作为参数。

 >>> list(yrange(5)) [0, 1, 2, 3, 4] >>> sum(yrange(5)) 10 

在上面的例子中,迭代器和迭代器都是同一个对象。 注意iter方法返回self。 它不一定是这种情况。

 class zrange: def __init__(self, n): self.n = n def __iter__(self): return zrange_iter(self.n) class zrange_iter: def __init__(self, n): self.i = 0 self.n = n def __iter__(self): # Iterators are iterables too. # Adding this functions to make them so. return self def next(self): if self.i < self.n: i = self.i self.i += 1 return i else: raise StopIteration() 

如果迭代器和迭代器都是同一个对象,它将在一次迭代中被消耗。

 >>> y = yrange(5) >>> list(y) [0, 1, 2, 3, 4] >>> list(y) [] >>> z = zrange(5) >>> list(z) [0, 1, 2, 3, 4] >>> list(z) [0, 1, 2, 3, 4] 

5.2。 发电机

生成器简化了迭代器的创build。 生成器是一个函数,它产生一个结果序列而不是一个单一的值。

 def yrange(n): i = 0 while i < n: yield i i += 1 

yield函数每次执行时都会生成一个新的值。

 >>> y = yrange(3) >>> y <generator object yrange at 0x401f30> >>> y.next() 0 >>> y.next() 1 >>> y.next() 2 >>> y.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> 

的StopIteration

所以一个生成器也是一个迭代器。 你不必担心迭代器协议。

“发电机”这个词被混淆地用来表示产生的function和产生的function。 在本章中,我将使用“生成器”一词来表示生成的对象,“生成器函数”表示生成它的函数。

你能想一下它是如何在内部工作的吗?

当一个生成器函数被调用时,它将返回一个生成器对象,甚至不用开始执行该函数。 当第一次调用下一个方法时,函数开始执行,直到达到yield语句为止。 下一次调用返回的值。

以下示例演示了yield和调用发生器对象的下一个方法之间的相互作用。

 >>> def foo(): ... print "begin" ... for i in range(3): ... print "before yield", i ... yield i ... print "after yield", i ... print "end" ... >>> f = foo() >>> f.next() begin before yield 0 0 >>> f.next() after yield 0 before yield 1 1 >>> f.next() after yield 1 before yield 2 2 >>> f.next() after yield 2 end Traceback (most recent call last): File "<stdin>", line 1, in <module> 

的StopIteration

让我们看一个例子:

 def integers(): """Infinite sequence of integers.""" i = 1 while True: yield i i = i + 1 def squares(): for i in integers(): yield i * i def take(n, seq): """Returns first n values from the given sequence.""" seq = iter(seq) result = [] try: for i in range(n): result.append(seq.next()) except StopIteration: pass return result print take(5, squares()) # prints [1, 4, 9, 16, 25]