【问题标题】:izip_longest in itertools: what's going on here?itertools 中的 izip_longest:这是怎么回事?
【发布时间】: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

【问题讨论】:

    标签: python itertools


    【解决方案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 中每个可迭代对象的迭代器。这些迭代器中的每一个都执行以下操作:

    1. 遍历来自args的相应可迭代对象中的所有项。
    2. 迭代哨兵一次,产生fillvalue
    3. 永远重复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),而不是在每次调用中重新构造它。

      【讨论】:

        猜你喜欢
        • 2011-07-06
        • 2011-12-17
        • 2013-07-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-28
        • 2022-01-23
        相关资源
        最近更新 更多