为什么Python的itertools不被归类为生成器(GeneratorType)?

5
我刚刚发现各种itertools函数返回的类类型不被Python类型系统视为生成器。
首先,让我们来看一下设置:
import collections
import glob  
import itertools
import types

ig = glob.iglob('*')
iz = itertools.izip([1,2], [3,4])

然后:

>>> isinstance(ig, types.GeneratorType) 
True
>>> isinstance(iz, types.GeneratorType)
False
glob.iglob()的结果,或者任何典型的生成器,都是types.GeneratorType类型。但是itertools的结果不是这样的。如果我想编写一个必须急切地评估输入序列的函数,这会导致很多混乱 - 我需要知道它是否是生成器。
我找到了这个替代方案:
>>> isinstance(ig, collections.Iterator)
True
>>> isinstance(iz, collections.Iterator)
True

但这并不是最理想的,因为无论 x 是一个具体(立即计算)的序列还是一个生成器(延迟计算),iter(x) 都是一个 Iterator

最终目标是像这样:

def foo(self, sequence):
    """Store the sequence, making sure it is fully
    evaluated before this function returns."""

    if isinstance(sequence, types.GeneratorType):
        self.sequence = list(sequence)
    else:
        self.sequence = sequence

我希望这样做的一个例子是,如果序列的评估可能会引发异常,并且我希望该异常从foo()而不是从后续使用self.sequence中引发。
我不喜欢types.GeneratorType的方法,因为它会产生一些误报--我不想不必要地构造输入列表的副本,因为它可能很大。
我愿意忽略“不寻常”的迭代器,这意味着如果有人实现了一个自定义迭代器,但不符合生成器的条件,我可以忽略它,但我不愿意对itertools采用错误的行为,因为它们相当受欢迎。

2
根据文档GeneratorType是“通过调用生成器函数产生的生成器迭代器对象的类型”。iglob是通过调用带有yield的Python函数_iglob来实现的,但izip是用C实现的。 - jonrsharpe
找对了分支花了我一些时间,但是你可以在这里找到:https://hg.python.org/cpython/file/2.7/Modules/itertoolsmodule.c#l3491 - jonrsharpe
@jonrsharpe:感谢你提供的 hg 链接。它显示 izip_typetp_base 为 null。我想知道为什么会这样——tp_base 在 Python 2.2 中被添加,而 itertools 是在 2.3 中添加的,所以它们可以使用它,但却没有使用。我不确定是因为时间不对劲,还是因为 itertools 类型不想子类化 GeneratorType,或者有一些具体的原因。 - John Zwinck
作为提示,您可以使用isinstance(sequence, (types.GeneratorType, collections.Iterator))进行检查。 - Netwave
1个回答

6

为什么Python的itertools没有被归类为生成器?

生成器视为实现迭代器的众多可能方式之一。itertools都是用C编写的自定义迭代器。其中大部分可以使用生成器编写较慢的代码来实现,但它们被设计用于提高速度。

types.GeneratorType 指的是“由调用生成器函数产生的生成器迭代器对象的类型”。由 glob.iglob() 返回的迭代器是通过调用生成器函数产生的,因此它将匹配生成器类型。然而,由 itertools.izip() 返回的迭代器是由 C 代码生成的,因此它不会匹配生成器类型。

换句话说,types.GeneratorType 对于识别所有惰性评估的迭代器并不有用,它只对于识别实际的生成器迭代器有用。

如何识别完全评估的集合?

似乎目标是区分“急切评估”集合(如列表、元组、字典和集合)与“惰性评估”迭代器。使用 collections.Iterator 可能是正确的方法:
>>> isinstance([], collections.Iterator)
False
>>> isinstance((), collections.Iterator)
False
>>> isinstance({}, collections.Iterator)
False
>>> isinstance(set(), collections.Iterator)
False

>>> isinstance(iter([]), collections.Iterator)
True
>>> isinstance(iter(()), collections.Iterator)
True
>>> isinstance(iter({}), collections.Iterator)
True
>>> isinstance(iter(set()), collections.Iterator)
True

>>> isinstance(glob.iglob('.'), collections.Iterator)
True
>>> isinstance(itertools.izip('abc', 'def'), collections.Iterator)
True
>>> isinstance((x**2 for x in range(5)), collections.Iterator)
True

如果已经调用了iter()会怎样?

如果您已经在任何“热心”的集合上调用了iter(),那么想要弄清楚上游可迭代对象的性质就为时已晚,除非采取一些诡计,例如type(x) in {type(iter(s)) for s in ([], (), {}, set())}

最终目标

所述目标是“存储序列,并确保在此函数返回之前完全评估”。通常的方法只需使用list(sequence)而不需要检查它是否已经是列表、元组、双端队列或其他完全评估的序列。这可能看起来很浪费,但list()调用非常快(它只是以C速度复制对象指针)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接