Python for循环和迭代器行为

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

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

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

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循环下的是什么?

最佳答案
您的怀疑是正确的:迭代器已被消耗。

实际上,你的迭代器是一个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!

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

>生成器不是指针,并且不像您可能在其他语言中熟悉的指针。
>与其他语言的区别之一:如上所述,生成器的每个结果都是即时生成的。在请求之前不产生下一个结果。
>用于in的关键字组合接受可迭代对象作为其第二个参数。
> iterable对象可以是一个生成器,如在你的示例case,但它也可以是任何其他可迭代对象,如列表,或dict,或str对象(字符串)或用户定义类型,提供所需功能。
> iter function应用于对象以获得一个迭代器(顺便说一下:不要在Python中使用iter作为变量名,正如你所做的 – 它是一个关键字)。实际上,为了更精确,调用对象的__iter__ method(这在很大程度上是所有的iter函数; __iter__是Python的所谓的“魔术方法”之一)。
>如果对__iter__的调用成功,则函数next()在循环中反复应用于可迭代对象,并且提供给for in的第一个变量被分配给next()函数的结果。 (记住:可迭代对象可以是一个生成器,一个容器对象的迭代器,或任何其他可迭代对象。)实际上,更精确的是:它调用迭代器对象的__next__方法,这是另一个“魔法”。
> for循环结束,next()引发StopIteration异常(当调用next()时,iterable没有另一个对象产生)。

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

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。Using in本身产生了与in不同的效果,但是了解它的参数是什么非常有用,因为对于in implements非常类似的行为。

>当自己使用时,in关键字首先调用对象的__contains__ method,这是另一个“魔法”(请注意,在使用for 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中的一个iterator.基本上,迭代器是一个对象,你可以使用内置的通用函数next() on1。生成器只是一种类型的迭代器。
>如果对__iter__的调用成功,则in关键字会一次又一次地将函数next()应用于可迭代对象。 (记住:可迭代对象可以是一个生成器,容器对象的迭代器,或任何其他可迭代对象。)实际上,更精确的是:它调用迭代器对象的__next__方法)。
>如果对象没有返回迭代器的__iter__方法,那么就会使用对象的__getitem__方法2来恢复旧式迭代协议。
>如果所有上述尝试都失败,你会得到一个TypeError exception

如果你想创建自己的对象类型来迭代(即,你可以使用in或者in),有用的是知道yield关键字,它在generators(如上所述)中使用。

class MyIterable():
    def __iter__(self):
        yield 1

m = MyIterable()
for _ in m: print(_) # 1
1 in m # True    

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

如果你想创建自己的容器对象类型(即,你可以使用它自己,但不是for),你只需要__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注意,作为一个迭代器,对象必须实现the iterator protocol.这只意味着__next__和__iter__方法都必须正确实现(生成器带有这个功能“免费”,所以你不需要担心它)当使用它们时)。还要注意___next__方法is actually next (no underscores) in Python 2

2有关创建可迭代类的不同方法,请参阅this answer

转载注明原文:Python for循环和迭代器行为 - 代码日志