关于python:如何区分迭代器和iterable?

How to tell the difference between an iterator and an iterable?

在python中,iterable的接口是迭代器接口的一个子集。这样做的好处是,在许多情况下,它们都可以用同样的方法进行治疗。但是,这两者之间有一个重要的语义差异,因为对于一个不可迭代的__iter__返回一个新的迭代器对象,而不仅仅是self。如何测试iterable是否真的是iterable而不是迭代器?从概念上讲,我将iterables理解为集合,而迭代器只管理迭代(即跟踪位置),而不是集合本身。

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

测试next方法是很有诱惑力的,但这看起来很危险,而且有点错误。我应该检查一下第二个循环是否是空的吗?

有没有什么方法可以用更为Python式的方式来做这样的测试?我知道这听起来像一个经典的LBYL反对EAFP的案例,所以也许我应该放弃?还是我错过了什么?

编辑:洛特在下面的回答中说,这主要是一个想要在迭代器上进行多次传递的问题,一开始不应该这样做。但是,在我的例子中,数据非常大,根据具体情况,必须多次传递以进行数据处理(绝对没有办法解决这个问题)。

iterable也由用户提供,在单次传递足够的情况下,它将使用迭代器(例如,为了简单起见,由生成器创建)。但是,如果用户在需要多个过程时只提供迭代器,那么最好防止出现这种情况。

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


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


However, there is an important semantic difference between the two...

不是真正意义上的或重要的。它们都是不可更改的——它们都使用for语句。

The difference is for example important when one wants to loop multiple times.

这是什么时候发生的?你得更具体一点。在极少数情况下,当您需要通过一个不可重复的集合进行两次传递时,通常会有更好的算法。

例如,假设您正在处理一个列表。您可以根据需要迭代一个列表。为什么你被一个迭代器而不是iterable缠住了?好吧,那不管用。

好的,这是一个。你在两次读取一个文件,你需要知道如何重置iterable。在这种情况下,它是一个文件,需要seek;或者关闭并重新打开。感觉恶心。您可以使用ecx1〔1〕获得一个列表,允许两次通过而不复杂。所以这是不必要的。

等等,如果我们有一个文件这么大,我们不能把它全部读到内存中怎么办?而且,由于不明原因,我们也找不到。那么呢?

现在,我们只剩下两次传球了。第一次,我们积累了一些东西。索引、摘要或其他东西。索引包含文件的所有数据。摘要通常是对数据的重组。通过从"摘要"到"重新构造"的微小更改,我们将文件的数据保存在新的结构中。在这两种情况下,我们都不需要文件——我们可以使用索引或摘要。

所有"两通"算法都可以更改为原始迭代器或iterable的一通,以及不同数据结构的第二通。

这既不是lybl也不是eafp。这是算法设计。您不需要重置迭代器——yagni。

编辑

下面是一个迭代器/可迭代问题的例子。这只是一个设计糟糕的算法。

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

这是微不足道的固定。

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

"多次并联"是一成不变的。编写一个需要ITerable的API。当有人拒绝阅读API文档或者在阅读之后拒绝跟随它时,他们的东西就会断裂。应该如此。

"如果一个用户在需要多次传递时只提供一个迭代器,那么很好地防止出现这种情况",这两个例子都是疯狂的人编写的代码破坏了我们的简单API。

如果有人精神错乱,足以阅读大部分(但不是所有的API文档),并在需要iterable时提供迭代器,那么您需要找到此人并教他们(1)如何阅读所有的API文档,以及(2)遵循API文档。

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

编辑2

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

不要这样做。

1
2
3
4
5
for element in someBigIterable:
    function1( element )
for element in someBigIterable:
    function2( element )
...

改为这样做。

1
2
3
4
for element in someBigIterable:
    function1( element )
    function2( element )
    ...

或者,考虑这样的事情。

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

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


1
2
3
4
5
6
7
8
9
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:

雷蒙德从吉多借来的该死的时光机器…


因为Python的鸭子打字,

如果定义next()__iter__()方法返回自身,则任何对象都是不可重写的。

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

您可以参考这个问题来了解Python中的ITerability。