itertools中的izip_longest:这里发生了什么?

8

我很难理解下面的代码是如何工作的。它来自于http://docs.python.org/library/itertools.html#itertools.izip_longest,是 izip_longest 迭代器的纯 Python 等效版本。我对 sentinel 函数特别感到神秘,它是如何工作的呢?

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
3个回答

6

好的,我们可以做到。关于sentinel。表达式([fillvalue]*(len(args)-1))创建了一个列表,其中包含每个可迭代对象在args中的一个填充值,减去一个。所以,对于上面的例子是['-']counter然后被分配为该列表的pop函数。sentinel本身是一个生成器,它在每次迭代中从该列表中弹出一个项目。您可以仅一次地迭代由sentinel返回的每个迭代器,并且它将始终产生fillvalue。由sentinel返回的所有迭代器产生的项目总数为len(args) - 1(感谢Sven Marnach澄清,我误解了它)。

现在看看这个:

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

那就是诀窍。 iters 是一个列表,其中包含args中每个可迭代对象的迭代器。每个迭代器都执行以下操作:
  1. 迭代args中对应可迭代对象中的所有项目。
  2. 一次迭代sentinel,产生fillvalue
  3. 永远重复fillvalue
现在,正如之前所述,我们只能在抛出IndexError之前将所有标记一起迭代len(args)-1次。这没问题,因为其中一个可迭代对象是最长的。所以,当我们到达抛出IndexError的点时,这意味着我们已经完成了对args中最长可迭代对象的迭代。
不客气。
附言:我希望这可以理解。

2
此外,sentinel() 可以被无限次调用而不会引发任何异常。 - Sven Marnach

5
函数sentinel()返回生成器迭代器,只会产生一个fillvalue。由sentinel()返回的所有迭代器产生的fillvalue总数限制为n-1,其中n是传递给izip_longest()的迭代器数量。在耗尽此数量的fillvalue后,进一步迭代由sentinel()返回的迭代器将引发IndexError
此函数用于检测所有迭代器是否已耗尽:每个迭代器都与由sentinel()返回的迭代器进行链接。如果所有迭代器都已耗尽,则由sentinel()返回的迭代器将被迭代第n次,导致IndexError,从而依次触发izip_longest()的结束。
到目前为止,我解释了sentinel()的功能,但没有解释它的工作原理。调用izip_longest()时,将评估sentinel()的定义。在评估定义时,还会评估sentinel()的默认参数,每次调用izip_longest()都会这样做。该代码等效于:
fillvalue_list = [fillvalue] * (len(args)-1)
def sentinel():
    yield fillvalue_list.pop()

将这个值存储在默认参数中而不是在封闭作用域的变量中只是一种优化,同样地,在默认参数中包含 .pop 也是一种优化,因为它避免了每次迭代由 sentinel() 返回的迭代器时都要查找它。


2
sentinel的定义几乎等同于
def sentinel():
    yield ([fillvalue] * (len(args) - 1)).pop()

除了将pop绑定方法(一个函数对象)作为默认参数外,它与常规的函数调用没有什么不同。默认参数在函数定义时计算,因此每次调用izip_longest而不是每次调用sentinel都会计算一次。因此,函数对象“记住”列表[fillvalue] * (len(args) - 1),而不是在每次调用中重新构造。


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