Python for循环和迭代器行为

Matteo:

我想了解更多有关的信息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循环幕后到底发生了什么?

瑞克支持莫妮卡:

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

实际上,您的迭代器是一个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对象(字符串)或提供所需功能的用户定义类型。
  • iter函数将应用于对象以获取迭代器(顺便说一句:iter正如您所做的那样,不要在Python中用作变量名-它是关键字之一)。实际上,更确切地说,是调用对象的__iter__方法(在大多数情况下,所有iter函数可以执行;这__iter__是Python所谓的“魔术方法”之一)。
  • 如果调用__iter__成功,则next()循环将函数一次又一次地应用于可迭代对象,并将提供给的第一个变量for in分配给next()函数的结果(请记住:可迭代对象可以是生成器,也可以是容器对象的迭代器,或任何其他可迭代对象。)实际上,更准确地说:它调用了迭代器对象的__next__方法,这是另一个“魔术方法”。
  • for循环在next()引发StopIteration异常时结束(通常在可迭代对象next()被调用时没有其他对象产生时发生)。

您可以通过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单独使用in会产生与产生不同的效果for in,但是了解in使用其参数的作用非常有用,因为它for in实现的行为非常相似。

  • 单独使用时,in关键字首先调用对象的__contains__method,这是另一个“魔术方法”(请注意,使用时将跳过此步骤for in)。使用in一个容器本身,你可以做这样的事情:

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

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

如果您希望创建自己的对象类型以进行迭代(即,您可以使用,也可以for inin在其上使用),那么了解生成器中yield使用关键字(如上所述)很有用

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

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

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

如果您希望创建自己的容器对象类型(即可以单独使用in它,但不能使用for 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__方法都必须正确实现(生成器带有“免费”此功能,因此您在使用它们时无需担心)。还要注意,该___next__方法实际上next在Python 2中(没有下划线)

2有关创建可迭代类的不同方法,请参见此答案

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章