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]
)不会打印任何东西。
但是我从来没有给iter
variables赋值。
在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
的关键字组合接受一个可迭代对象作为其第二个参数。 - 可迭代的对象可以是一个生成器,就像在你的例子中那样,但是它也可以是任何其他可迭代的对象,比如
list
,dict
或者str
对象(string),或者是一个用户定义的types,function。 -
iter
函数被应用到对象来获得一个迭代器(顺便说一下:不要在Python中使用iter
作为variables名,就像你所做的那样 – 它是关键字之一)。 实际上,更确切地说,调用了对象的__iter__
方法 (大多数情况下,它的所有iter
函数都是这样做的;__iter__
是Python所谓的“魔术方法”之一)。 - 如果对
__iter__
的调用成功,则next()
函数将循环应用于可迭代对象,并将为for
提供的第一个variables分配给next()
函数的结果。 (记住:可迭代的对象可能是一个生成器,或者一个容器对象的迭代器,或者任何其他可迭代对象。)实际上,更确切地说:它调用迭代器对象的__next__
方法,这是另一种“神奇的方法”。 - 当
next()
引发StopIteration
exception(通常在迭代器调用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__
方法返回到旧式迭代协议。 - 如果所有上述尝试失败,您将得到一个
TypeError
exception 。
如果您希望创build自己的对象types来迭代(即,您可以使用in
或in
,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))
将导致StopIteration
exception抛出。 此时将停止。 迭代器可以是任何将响应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__
使用0
– len(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]