itertools中的izip_longest: 这是什么情况?
我在努力理解下面的代码是怎么工作的。这段代码来自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 个回答
sentinel的定义几乎等同于
def sentinel():
yield ([fillvalue] * (len(args) - 1)).pop()
不过它将pop这个方法(一个函数对象)作为默认参数。默认参数是在函数定义的时候就被计算的,所以每次调用izip_longest时只会计算一次,而不是每次调用sentinel时都计算。因此,这个函数对象会“记住”列表[fillvalue] * (len(args) - 1),而不是在每次调用时都重新构造这个列表。
这个函数 sentinel() 会返回一些迭代器,这些迭代器会输出 fillvalue 这个值一次。所有通过 sentinel() 返回的迭代器总共只能输出 n-1 次 fillvalue,其中 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() 返回的迭代器时都去查找。
好的,我们来讲讲这个哨兵(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 中可迭代对象的迭代器。每个迭代器的工作如下:
- 遍历对应的
args中的所有项目。 - 遍历一次
sentinel,返回fillvalue。 - 永远重复返回
fillvalue。
现在,正如之前所说,我们只能一起遍历所有的哨兵 len(args)-1 次,之后就会抛出一个 IndexError。这没关系,因为其中一个可迭代对象是最长的。所以,当我们遇到 IndexError 时,这意味着我们已经完成了对 args 中最长可迭代对象的遍历。
不客气。
附言:希望这样讲你能理解。