itertools 中的 izip_longest:这是怎么回事?

Posted

技术标签:

【中文标题】itertools 中的 izip_longest:这是怎么回事?【英文标题】:izip_longest in itertools: what's going on here? 【发布时间】:2011-03-14 12:40:37 【问题描述】:

我正在努力理解以下代码的工作原理。它来自http://docs.python.org/library/itertools.html#itertools.izip_longest,是 izip_longest 迭代器的纯 python 等价物。我对哨兵功能特别困惑,它是如何工作的?

def izip_longest(*args, **kwds):
    # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
    fillvalue = kwds.get('fillvalue')
    def sentinel(counter = ([fillvalue]*(len(args)-1)).pop):
        yield counter()         # yields the fillvalue, or raises IndexError
    fillers = repeat(fillvalue)
    iters = [chain(it, sentinel(), fillers) for it in args]
    try:
        for tup in izip(*iters):
            yield tup
    except IndexError:
        pass

【问题讨论】:

【参考方案1】:

好的,我们可以做到这一点。关于哨兵。表达式([fillvalue]*(len(args)-1)) 创建一个列表,其中包含args 中每个可迭代对象的一个​​填充值,减一。所以,对于上面的例子['-']。然后将counter 分配给该列表的pop 函数。 sentinel 本身是一个 generator,它在每次迭代时从该列表中弹出一个项目。您可以只对sentinel 返回的每个迭代器进行一次迭代,它总是会产生fillvaluesentinel 返回的所有迭代器产生的项目总数为 len(args) - 1(感谢 Sven Marnach 澄清,我误解了它)。

现在看看这个:

iters = [chain(it, sentinel(), fillers) for it in args]

这就是诀窍。 iters 是一个列表,其中包含 args 中每个可迭代对象的迭代器。这些迭代器中的每一个都执行以下操作:

    遍历来自args的相应可迭代对象中的所有项。 迭代哨兵一次,产生fillvalue。 永远重复fillvalue

现在,如前所述,我们只能在抛出IndexError 之前将所有哨兵一起迭代len(args)-1 次。这很好,因为其中一个可迭代对象是最长的。因此,当我们遇到 IndexError 被引发时,这意味着我们已经完成了对 args 中最长可迭代对象的迭代。

不客气。

P.S.:我希望这是可以理解的。

【讨论】:

另外,sentinel() 可以无限次调用,不会引发任何异常。【参考方案2】:

函数sentinel() 返回迭代器,只产生一次fillvaluesentinel() 返回的所有迭代器产生的fillvalues 总数限制为n-1,其中n 是传递给izip_longest() 的迭代器数量。在这个数量的fillvalues 用完后,对sentinel() 返回的迭代器的进一步迭代将引发IndexError

此函数用于检测是否所有迭代器都已耗尽:每个迭代器都被chain()ed 与sentinel() 返回的迭代器。如果所有迭代器都用完,sentinel() 返回的迭代器将被迭代nth 次,产生IndexError,依次触发izip_longest() 的结束。

到目前为止,我解释了sentinel() 的作用,而不是它的工作原理。当调用izip_longest() 时,会评估sentinel() 的定义。在评估定义时,还会评估 sentinel() 的默认参数,每次调用 izip_longest() 一次。代码相当于

fillvalue_list = [fillvalue] * (len(args)-1)
def sentinel():
    yield fillvalue_list.pop()

将其存储在默认参数中而不是封闭范围内的变量中只是一种优化,在默认参数中包含.pop 也是如此,因为它可以节省每次@987654344 返回的迭代器时查找它@ 被迭代了。

【讨论】:

【参考方案3】:

sentinel的定义几乎等价于

def sentinel():
    yield ([fillvalue] * (len(args) - 1)).pop()

除了它将pop 绑定方法(一个函数对象)作为默认参数。默认参数在函数定义时进行评估,因此每次调用izip_longest 一次,而不是每次调用sentinel 一次。因此,函数对象“记住”列表[fillvalue] * (len(args) - 1),而不是在每次调用中重新构造它。

【讨论】:

以上是关于itertools 中的 izip_longest:这是怎么回事?的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Python 3.5.2 中导入 itertools

如何理解使用 izip_longest 对列表进行分块的代码?

未能导入名为“版本”的模块,因为 izip_longest

python中的itertools.groupby()

防止 itertools.permutation 中的内存错误

Python中的字典分组函数(groupby,itertools)