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
返回的每个迭代器进行一次迭代,它总是会产生fillvalue
。 sentinel
返回的所有迭代器产生的项目总数为 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()
返回迭代器,只产生一次fillvalue
。 sentinel()
返回的所有迭代器产生的fillvalue
s 总数限制为n-1
,其中n
是传递给izip_longest()
的迭代器数量。在这个数量的fillvalue
s 用完后,对sentinel()
返回的迭代器的进一步迭代将引发IndexError
。
此函数用于检测是否所有迭代器都已耗尽:每个迭代器都被chain()
ed 与sentinel()
返回的迭代器。如果所有迭代器都用完,sentinel()
返回的迭代器将被迭代n
th 次,产生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 对列表进行分块的代码?