【问题标题】:Difference between list comprehension and generator comprehension with `yield` inside列表理解和生成器理解之间的区别,里面有`yield`
【发布时间】:2016-05-04 04:55:31
【问题描述】:

内含yield 的列表推导和生成器推导有什么区别?两者都返回一个生成器对象(分别为listcompgenexpr),但在全面评估后,后者添加了似乎相当多余的Nones。

>>> list([(yield from a) for a in zip("abcde", itertools.cycle("12"))])
['a', '1', 'b', '2', 'c', '1', 'd', '2', 'e', '1']

>>> list(((yield from a) for a in zip("abcde", itertools.cycle("12"))))
['a', '1', None, 'b', '2', None, 'c', '1', None, 'd', '2', None, 'e', '1', None]

怎么会?科学解释是什么?

【问题讨论】:

  • @Alik,@Antti Haapala,请删除“重复”标记。这个问题询问使用yield from Python 语句的行为。链接的“重复”答案询问了关于yield Python 语句的类似问题。这两种说法是不同的。而且由于yield from 是最近才添加到该语言中的,因此很自然地会产生新的意外行为。这应该会导致一些问题,虽然它们可能看起来类似于关于 yield 的问题,但与关于 yield 的问题不同。
  • 您还在寻找问题的答案吗?
  • 第一种情况实际上是丢弃列表推导的结果。结果是由 yield from a 引起的理解的副作用。

标签: python python-3.x list-comprehension


【解决方案1】:

TLDR:生成器表达式使用隐式yield,它从yield from 表达式返回None

实际上有 两个 事物在这里表现不同。你的列表理解实际上被扔掉了......

  • 再次清晰

如果将表达式转换为等效函数,则最容易理解这一点。为了清楚起见,让我们把它写出来:

listcomp = [<expr> for a in b]
def listfunc():
    result = []
    for a in b:
        result.append(<expr>)
    return result

gencomp = (<expr> for a in b)
def genfunc():
    for a in b:
        yield <expr>

要复制初始表达式,关键是将&lt;expr&gt; 替换为(yield from a)。这是一个简单的文本替换:

def listfunc():
    result = []
    for a in b:
        result.append((yield from a))
    return result

def genfunc():
    for a in b:
        yield (yield from a)

对于b = ((1,), (2,)),我们期望输出1, 2。事实上,两者都复制了各自表达/理解形式的输出。

正如elsewhere 解释的那样,yield (yield from a) 应该让你怀疑。但是,result.append((yield from a)) 应该会让你畏缩......

  • 给出答案

让我们先看看生成器。另一个重写使发生的事情变得显而易见:

def genfunc():
    for a in b:
        result = (yield from a)
        yield result

要使其有效,result 必须有一个值 - 即 None。生成器不是 yield (yield from a) 表达式,而是它的结果。您只能获得 a 的内容作为评估表达式的副作用。

  • 回到问题

如果您检查“列表理解”的类型,它不是list - 它是generator&lt;listcomp&gt; 只是它的名字。是的,那不是月球,那是一个功能齐全的发电机。

还记得我们的转换如何将yield from 放入函数中吗?是的,这就是你定义生成器的方式! 这是我们的函数版本,这次是print

def listfunc():
    result = []
    for a in b:
        result.append((yield from a))
        print(result[-1])
    print(result)
    return result

评估list(listfunc()) 打印 NoneNone(来自append)和[None, None](来自result)和产量 1, 2。您的实际列表也包含那些潜入生成器的None!然而,它被扔掉了,结果又只是一个副作用。这就是实际发生的情况:

  • 在评估列表理解/listfunc 时创建生成器。
  • 将其提供给list 会对其进行迭代...
    • yield from aa 的值生成到 list 并返回 None 到理解/listfunc
    • None 存储在结果列表中
  • 在迭代结束时...

    • return 引发 StopIteration 的值为 [None, None]
    • list 构造函数会忽略此值并丢弃该值
  • 这个故事的寓意

不要在推导式中使用yield from。它不会做你认为的那样。

【讨论】:

  • yield in comprehensions 和 genexps 在 Python 3.7 中将是 deprecated 而在 Python 3.8 中被禁止,因为它非常令人困惑并且违反了诸如“列表推导评估为列表”之类的不变量。
【解决方案2】:

yield from 表达式的值为None。您的第二个示例是生成器表达式这一事实意味着它已经隐式地从迭代器中产生,因此它也将产生 yield from 表达式的值。请参阅this 以获得更详细的答案。

【讨论】:

  • 它没有解释生成器理解和列表理解中yield from评估之间的区别
  • 列表推导没有隐含的收益,所以这种行为不会发生。
【解决方案3】:

这两个示例在 Python 3.8 中都已被弃用,因为混淆并抛出 SyntaxError: 'yield' inside list comprehension。请参阅buglog for 3.8 for the release notes

【讨论】:

    猜你喜欢
    • 2016-02-27
    • 1970-01-01
    • 2018-08-27
    • 1970-01-01
    • 2016-07-09
    • 2021-04-07
    • 2013-08-15
    • 2013-12-30
    相关资源
    最近更新 更多