【问题标题】:python iterable list comprehension in list comprehension列表理解中的python可迭代列表理解
【发布时间】:2020-12-03 18:43:13
【问题描述】:

我试图弄清楚为什么我的嵌套列表理解不起作用。代码:

def myzip(*args):
    try:
        x = [iter(i) for i in [*args]]
        while True:
        # for i in range(5):
            yield [next(ii) for ii in x]
            # I want to know why the commented line below won't work
            # yield [next(ii) for ii in [iter(i) for i in [*args]]]
            # or why this outputs [1, 'a']
            # return [next(ii) for ii in [iter(i) for i in [*args]]]
    except:
        pass


def main():
    print(list(myzip([1, 2, 3, 4, 5], ['a', 'b', 'c'])))

if __name__ == '__main__':
    main()

如果我将[iter(i) for i in [*args]] 分配给x 然后使用此列表理解[next(ii) for ii in x] 一切正常。输出将是:

[[1, 'a'], [2, 'b'], [3, 'c']]

但是如果我尝试省略变量 x 并在一个更大的列表理解(注释行)[next(ii) for ii in [iter(i) for i in [*args]]] 中执行它,它会进入无限循环。如果将无限循环替换为 for 循环(注释行),则输出为:

[[1, 'a'], [1, 'a'], [1, 'a'], [1, 'a'], [1, 'a']]

此外,如果我尝试 return [next(ii) for ii in [iter(i) for i in [*args]]] 它只会返回:

[1, 'a']

谁能告诉我这是为什么?

【问题讨论】:

  • 为什么不应该呢?您没有在任何地方保存以前的值,而是在执行全新的列表理解。
  • 就像for i in range(10):while True: i = next(range(10))的区别
  • 第二个每次迭代都会创建一个新的范围对象并从头开始。
  • 我不这么认为,因为您不能将yield 放在列表理解中。
  • 对单线的痴迷是什么?

标签: python list-comprehension


【解决方案1】:

所以我不会直接回答你的问题(因为 cmets 已经很好地涵盖了这一点),但我将展示如何将其作为一个 可怕 一个班轮来完成,只是为了好玩:

def my_zip(*args):
    yield from iter(lambda data=[iter(a) for a in args]: [next(d) for d in data], None)

一共有三个部分。

  • iter 的第一次使用,在yield from 之后,创建一个callable_iterator。这将重复调用它的第一个参数(lambda),直到它看到它的第二个参数(标记值,在本例中为 None)或 它得到一个 StopIteration 错误它遇到 任何异常。 (在这种情况下,这将是一个StopIteration 异常。
  • lambda 中我有一个data 的默认值,它是您要迭代的迭代器的列表。 this 被创建一次,因此对函数的每个后续调用都将引用 this。
  • 最后,在lambda 的主体中,我在每个函数调用中手动推进data 中的每个迭代器一次。这将一直持续到其中一个迭代器用尽,此时将引发 StopIteration如您所见,在这种情况下,哨兵值无关紧要,因为第一个 iter 调用将在它获得其StopIteration 后立即保释 这将冒泡到@987654339 @consumer 导致它停止迭代。因为异常,sentinel 值无关紧要。

最后:请永远不要在实际代码中这样做。


编辑:一些示例输出

In [90]: list(my_zip('abcd', [1,2,3]))
Out[90]: [['a', 1], ['b', 2], ['c', 3]]

In [91]: list(my_zip('abcd', [1,2,3,4,5,6]))
Out[91]: [['a', 1], ['b', 2], ['c', 3], ['d', 4]]

解释编辑:向@superb rain 致敬,感谢他们下面的智能 cmets。

【讨论】:

  • 你怎么知道callable_iterator处理StopIteration?我对此表示怀疑,并且在文档中没有看到它。
  • 我认为处理它的是迭代器的消费者,而不是迭代器。在这种情况下list。如果我没记错的话,是消费者不断向迭代器询问越来越多的值,直到它得到一个 StopIteration,此时消费者停止询问。对于list,我没有看到它记录在案,但for documentation 谈到了这一点。
  • 其实,你是对的!是 list 消费者正在捕捉 StopIteration,这是有道理的。因为它知道如何停止迭代它正在消耗的东西。我会更新我的答案。很好的收获。
  • 好的 :-)。但你现在怎么知道?我认为list,但我不确定。也可能是 both 处理 StopIteration,只是 iter 然后重新提升它(尽管我不明白它为什么会这样做)。而且我想不出一个测试来毫无疑问地证明到底发生了什么。可能需要阅读 CPython 源代码和一些相关背景知识(以证明某事不是由iter 完成的)。
  • 啊哈!虽然我之前的评论是正确的,但尝试使用 StopIteration 进行演示是行不通的。所以这里还有一个测试表明 iter 确实 捕获 StopIteration: repl.it/repls/TroubledUnlawfulAtom#main.py 有两点需要注意:1)迭代器不允许我们在 StopIteration 之后要求进一步的值,并且2) 它提出了一个不同的 StopIteration,而不是f 提出的那个。可能是因为迭代器是documented,以便在第一个迭代器之后继续提高 StopIteration。
猜你喜欢
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-14
  • 1970-01-01
相关资源
最近更新 更多