如何区分迭代器和迭代?

时间:2009-04-02 09:56:56

标签: python iterator

在Python中,iterable的接口是iterator interface的子集。这具有的优点是,在许多情况下,它们可以以相同的方式处理。但是,两者之间存在重要的语义差异,因为对于可迭代的__iter__,返回一个新的迭代器对象而不仅仅是self。我怎样才能测试一个iterable真的是一个可迭代的而不是一个迭代器?从概念上讲,我理解iterables是集合,而迭代器只管理迭代(即跟踪位置),但不是集合本身。

例如,当想要多次循环时,差异很重要。如果给出了迭代器,那么第二个循环将不起作用,因为迭代器已经用完并直接引发StopIteration

测试next方法很有诱惑力,但这看起来很危险,而且有些不对。我应该检查第二个循环是否为空?

有没有办法以更加pythonic的方式进行这样的测试?我知道这听起来像是针对EAFP的LBYL的经典案例,所以也许我应该放弃?或者我错过了什么?

修改 S.Lott在下面的回答中说,这主要是一个想要在迭代器上进行多次传递的问题,并且首先不应该这样做。但是,在我的情况下,数据非常大,并且根据情况必须多次传递以进行数据处理(绝对没有办法解决这个问题)。

迭代也由用户提供,并且对于单次传递足够的情况,它将与迭代器一起工作(例如,为了简单起见,由生成器创建)。但是,如果用户在需要多次传递时只提供迭代器,那么防止这种情况会很好。

编辑2: 实际上这是Abstract Base Classes的一个非常好的例子。迭代器和迭代中的__iter__方法具有相同的名称,但是在语义上是不同的!因此hasattr无用,但isinstance提供了一个干净的解决方案。

4 个答案:

答案 0 :(得分:13)

'iterator' if obj is iter(obj) else 'iterable'

答案 1 :(得分:4)

  

然而,两者之间存在重要的语义差异......

不是真正的语义或重要。它们都是可迭代的 - 它们都使用for语句。

  

例如,当想要多次循环时,差异很重要。

什么时候出现?你必须更加具体。在极少数情况下,当您需要通过可迭代集合进行两次传递时,通常会有更好的算法。

例如,假设您正在处理列表。您可以根据需要迭代列表。你为什么纠缠于迭代器而不是迭代?好吧那没用。

好的,这是一个。您正在两遍中读取文件,并且您需要知道如何重置可迭代文件。在这种情况下,它是一个文件,并且seek是必需的;或者关闭并重新开放。感觉很蠢。你可以readlines获得一个允许两次通过而没有复杂性的列表。所以这不是必要的。

等等,如果我们有一个如此大的文件,我们无法将其全部读入内存怎么办?并且,由于不明原因,我们也无法寻求。那么呢?

现在,我们已经完成了两次传球的细节。在第一关,我们积累了一些东西。索引或摘要或其他内容。索引包含所有文件的数据。总结通常是对数据进行重组。通过从“摘要”到“重组”的小改动,我们在新结构中保留了文件的数据。在这两种情况下,我们都不需要文件 - 我们可以使用索引或摘要。

所有“两次通过”算法都可以更改为原始迭代器的一次传递或可迭代,以及另一种不同数据结构的传递。

这既不是LYBL也不是EAFP。这是算法设计。您不需要重置迭代器 - YAGNI。


修改

以下是迭代器/可迭代问题的示例。它只是一个设计不佳的算法。

it = iter(xrange(3))
for i in it: print i,; #prints 1,2,3 
for i in it: print i,; #prints nothing

这很简单。

it = range(3)
for i in it: print i
for i in it: print i

“多次并行”是平凡的。编写需要可迭代的API。当有人拒绝阅读API文档或拒绝阅读它后,他们的东西就会破坏。应该如此。

“很好地防范案例是用户在需要多次传递时只提供迭代器”都是疯狂的人编写破坏我们简单API的代码的例子。

如果有人疯狂阅读大多数(但不是全部API文档)并在需要需要的时候提供迭代器,那么你需要找到这个人并教他们(1)如何阅读所有API文档和(2)遵循API文档。

“保障”问题不太现实。这些疯狂的程序员非常罕见。在少数情况下,你知道他们是谁并可以帮助他们。


修改2

“我们必须多次读取相同的结构”算法是一个基本问题。

不要这样做。

for element in someBigIterable:
    function1( element )
for element in someBigIterable:
    function2( element )
...

相反,请这样做。

for element in someBigIterable:
    function1( element )
    function2( element )
    ...

或者,考虑这样的事情。

for element in someBigIterable:
    for f in ( function1, function2, function3, ... ):
        f( element )

在大多数情况下,算法的这种“枢轴”导致程序可能更容易优化,并且可能是性能的净改进。

答案 2 :(得分:2)

import itertools

def process(iterable):
    work_iter, backup_iter= itertools.tee(iterable)

    for item in work_iter:
        # bla bla
        if need_to_startover():
            for another_item in backup_iter:

雷蒙德从Guido那里借来的那台该死的时间机器......

答案 3 :(得分:0)

由于Python的鸭子打字,

如果任何对象定义next()并且__iter__()方法返回自身,则该对象是可迭代的。

如果对象本身没有next()方法,__iter__()可以返回任何具有next()方法的对象

您可以参考此问题查看Iterability in Python