Python for循环和迭代器行为

时间:2015-04-02 01:03:17

标签: python iterator

我想更多地了解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循环的引擎下真正发生了什么?

6 个答案:

答案 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接受可迭代对象作为其第二个参数。
  • 可迭代对象可以是生成器,如示例中所示,但它也可以是任何其他可迭代对象,例如listdictstr 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。这只意味着forin方法必须正确实现(生成器具有此功能&#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 docs herehere

迭代器协议的工作方式通常是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__012重复调用...,直到它获得IndexError为止转换为StopIteration

有关创建迭代器的不同方法的更多详细信息,请参阅this answer

答案 4 :(得分:2)

摘录自the Python Practice book

5。迭代器&amp;发电机

5.1。迭代器

我们使用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']

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

在上面的例子中,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]

5.2。发电机

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

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异常

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

“生成器”这个词混淆地用来表示生成的函数和生成的函数。在本章中,我将使用“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