itertools中的izip_longest: 这是什么情况?

8 投票
3 回答
6439 浏览
提问于 2025-04-16 13:39

我在努力理解下面的代码是怎么工作的。这段代码来自http://docs.python.org/library/itertools.html#itertools.izip_longest,是纯Python实现的izip_longest迭代器。我特别困惑的是那个哨兵函数,它是怎么工作的?

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

2

sentinel的定义几乎等同于

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

不过它将pop这个方法(一个函数对象)作为默认参数。默认参数是在函数定义的时候就被计算的,所以每次调用izip_longest时只会计算一次,而不是每次调用sentinel时都计算。因此,这个函数对象会“记住”列表[fillvalue] * (len(args) - 1),而不是在每次调用时都重新构造这个列表。

5

这个函数 sentinel() 会返回一些迭代器,这些迭代器会输出 fillvalue 这个值一次。所有通过 sentinel() 返回的迭代器总共只能输出 n-1fillvalue,其中 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() 返回的迭代器时都去查找。

6

好的,我们来讲讲这个哨兵(sentinel)。表达式 ([fillvalue]*(len(args)-1)) 创建了一个列表,这个列表里包含了每个 args 中可迭代对象的填充值,数量比 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

现在,正如之前所说,我们只能一起遍历所有的哨兵 len(args)-1 次,之后就会抛出一个 IndexError。这没关系,因为其中一个可迭代对象是最长的。所以,当我们遇到 IndexError 时,这意味着我们已经完成了对 args 中最长可迭代对象的遍历。

不客气。

附言:希望这样讲你能理解。

撰写回答