我想更多地了解iterators
,所以如果我错了,请纠正我。
迭代器是一个对象,它具有指向下一个对象的指针,并被读取为缓冲区或流(即链接列表)。它们特别有效,因为他们所做的就是通过引用告诉你接下来的内容而不是使用索引。
但是我仍然不明白为什么会发生以下行为:
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
变量分配新值。
在for
循环的引擎下真正发生了什么?
答案 0 :(得分:53)
您的怀疑是正确的:迭代器已被消耗。
实际上,你的迭代器是generator,它是一个只能通过迭代一次的对象。
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!
其他一些更正有助于提高您的理解:
for
in
接受可迭代对象作为其第二个参数。 list
,dict
或str
object(字符串),或提供所需功能的用户定义类型。 iter
function应用于对象以获取迭代器(顺便说一下:不要在Python中使用iter
作为变量名,就像你所做的那样 - 它是关键词)。实际上,更准确地说,对象的__iter__
method被调用(在大多数情况下,所有iter
函数都会被调用; __iter__
是Python中的一个&# 39;所谓的"魔术方法")。__iter__
的调用成功,则函数next()
会一遍又一遍地在循环中应用于可迭代对象,并且第一个变量提供给for
{{ 1}}被分配给in
函数的结果。 (请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更确切地说:它调用迭代器对象的__next__
方法,这是另一种魔法"。 next()
循环在for
引发StopIteration
异常时结束(这通常发生在迭代器在调用next()
时没有另一个对象产生时)。< / LI>
你可以&#34;手动&#34;以这种方式在python中实现next()
循环(可能不完美,但足够接近):
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
for
不同的效果,但了解in
对其参数的作用非常有用,因为in
{{ 1}}实现非常相似的行为。
单独使用时,for
关键字会首先调用对象的__contains__
method,这是另一种&#34;魔术方法&#34; (请注意,使用in
in
时会跳过此步骤。在容器上单独使用for
,您可以执行以下操作:
in
如果可迭代对象不是容器(即它没有in
方法),1 in [1, 2, 3] # True
'He' in 'Hello' # True
3 in range(10) # True
'eH' in 'Hello'[::-1] # True
接下来会尝试调用对象{{1 }} 方法。如前所述:__contains__
方法将Python中已知的内容返回为iterator。基本上,迭代器是一个对象,您可以在 1 上使用内置泛型函数next()
。生成器只是一种迭代器。
in
的调用成功,则__iter__
关键字会一次又一次地将函数next()
应用于可迭代对象。 (请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更确切地说:它调用迭代器对象的__next__
方法)。 __iter__
方法返回迭代器,__iter__
则使用对象in
回退到旧式迭代协议方法 2 。 TypeError
exception。如果您希望创建自己的对象类型进行迭代(即,您可以使用__iter__
in
,或仅使用__getitem__
),那么它很有用要了解generators中使用的for
关键字(如上所述)。
in
in
的存在将函数或方法转换为生成器而不是常规函数/方法。如果您使用生成器(它会自动带来yield
),您就不需要class MyIterable():
def __iter__(self):
yield 1
m = MyIterable()
for _ in m: print(_) # 1
1 in m # True
方法。
如果您希望创建自己的容器对象类型(即,您可以单独使用yield
,而不是__next__
__next__
),则只需要{{1 }} 方法。
in
1 请注意,要成为迭代器,对象必须实现the iterator protocol。这只意味着for
和in
方法必须正确实现(生成器具有此功能&#34;免费&#34;,所以你不要&#39; ;使用时需要担心它)。另请注意__contains__
方法is actually next
(no underscores) in Python 2。
2 有关创建可迭代类的不同方法,请参阅this answer。
答案 1 :(得分:18)
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
此时输入对象中没有下一个元素。所以这样做:
print(next(iter))
将导致StopIteration
异常抛出。此时for
将停止。迭代器可以是any object,它将响应next()
函数,并在没有更多元素时抛出异常。它不必是任何指针或引用(在C / C ++意义上的python中没有这样的东西),链表等。
答案 2 :(得分:6)
python中有一个迭代器协议,用于定义for
语句对列表和dicts的行为以及其他可以循环的事物。
迭代器协议的工作方式通常是python生成器的形式。我们yield
一个值,只要我们有一个值,直到我们到达终点然后我们提出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
结果与上述相同。当我们进入第二个for循环时,它就会耗尽。
但对于更复杂的事情,这是相当简单的呢?也许在一个循环中为什么不呢?
def capital_iter(name):
for x in name:
yield x.upper()
raise StopIteration()
for y in capital_iter('bobert'):
print y
当它运行时,我们在字符串类型(内置于 iter 中)使用迭代器。反过来,这允许我们对它运行for循环,并产生结果,直到我们完成。
B
O
B
E
R
T
所以现在这引出了一个问题,那么迭代器中的收益之间会发生什么?
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()。
B
O
B
Hey There!
E
R
T
Traceback (most recent call last):
File "", line 13, in
StopIteration
答案 3 :(得分:4)
有iter()
个__getitem__
类缺少自己的__iter__
方法的行为的一些其他详细信息。
在__iter__
之前有__getitem__
。如果__getitem__
与int
- 0
中的len(obj)-1
一起使用,则iter()
支持这些对象。它将构造一个新的迭代器,使用__getitem__
,0
,1
,2
重复调用...
,直到它获得IndexError
为止转换为StopIteration
。
有关创建迭代器的不同方法的更多详细信息,请参阅this answer。
答案 4 :(得分:2)
我们使用for语句来循环列表。
>>> for i in [1, 2, 3, 4]:
... print i,
...
1
2
3
4
如果我们将它与字符串一起使用,它会遍历其字符。
>>> 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
因此有许多类型的对象可以与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']
内置函数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。
迭代器作为类实现。这是一个像内置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
许多内置函数接受迭代器作为参数。
>>> list(yrange(5))
[0, 1, 2, 3, 4]
>>> sum(yrange(5))
10
在上面的例子中,iterable和iterator都是同一个对象。请注意, 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()
如果iteratable和迭代器都是同一个对象,则会在一次迭代中使用它。
>>> 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]
生成器简化了迭代器的创建。生成器是一个生成结果序列而不是单个值的函数。
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>
所以生成器也是迭代器。您不必担心迭代器协议。
“生成器”这个词混淆地用来表示生成的函数和生成的函数。在本章中,我将使用“generator”一词来表示生成的对象,使用“generator function”来表示生成它的函数。
你能想一想它是如何在内部运作的吗?
当调用生成器函数时,它返回一个生成器对象,甚至没有开始执行该函数。当第一次调用next方法时,函数开始执行,直到达到yield语句。下一次调用将返回产生的值。
以下示例演示了yield和生成器对象上的next方法调用之间的相互作用。
>>> 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]
答案 5 :(得分:1)
概念1
所有生成器都是迭代器,但所有迭代器都不是生成器
概念2
迭代器是一个带有next(Python 2)或 next 的对象(Python 3) 方法
概念3
从wiki引用 Generators发电机 函数允许您声明一个行为类似的函数 迭代器,即它可以用于for循环。
在你的情况下
>>> it = (i for i in range(5))
>>> type(it)
<type 'generator'>
>>> callable(getattr(it, 'iter', None))
False
>>> callable(getattr(it, 'next', None))
True