【问题标题】:Unexpected results when comparing list comprehension with generator expression [duplicate]将列表理解与生成器表达式进行比较时出现意外结果 [重复]
【发布时间】:2020-02-16 09:03:58
【问题描述】:

我认为我忽略了一些简单的事情,但我似乎无法弄清楚到底是什么。请考虑以下代码:

a = [2, 3, 4, 5]

lc = [ x for x in a if x >= 4 ] # List comprehension
lg = ( x for x in a if x >= 4 ) # Generator expression

a.extend([6,7,8,9])

for i in lc:
    print("{} ".format(i), end="")

for i in lg:
    print("{} ".format(i), end="")

我预计两个 for 循环会产生相同的结果,所以 4 5。但是,打印生成器 exp 的 for 循环会打印 4 5 6 7 8 9。我认为这与列表理解的声明(在扩展之前声明)有关。但是为什么生成器的结果不同,因为它也是在扩展列表之前声明的?例如。内部情况如何?

【问题讨论】:

标签: python list generator


【解决方案1】:

在您调用 next() 之前不会对生成器进行评估,这使它们变得有用,而列表推导会立即评估。

所以lc = [4,5] 在扩展之前完成,因此完成。

lg 在开始时仍然是相同的值,因此 extend 仍然适用于尚未在生成器中完成评估的 a,这意味着 a 在您开始打印之前得到扩展这就是为什么它会打印出更长时间的其余数字。

这样检查:

>>> a = [2, 3, 4, 5]
>>> lg = ( x for x in a if x >= 4 )
>>> next(lg)
4
>>> next(lg)
5
>>> a.extend([6,7,8,9])
>>> next(lg)
6

但是,如果您在 extend 之前尝试调用额外的 next(),您将得到 StopIteration,因为此时生成器已耗尽,然后您将无法再调用它。

>>> a = [2, 3, 4, 5]
>>> lg = ( x for x in a if x >= 4 )
>>> next(lg)
4
>>> next(lg)
5
>>> next(lg)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a.extend([6,7,8,9])
>>> next(lg)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

【讨论】:

    【解决方案2】:

    内部发生了什么?

    生成器天生就是懒惰的。

    [ x for x in a if x &gt;= 4 ] 在执行后立即进行评估。

    ( x for x in a if x &gt;= 4 ) 当它执行时它只是创建生成器。仅当以多种可能的方式之一使用生成器时,循环本身才会被评估/执行(“手动”调用next,转换为另一种可迭代类型[list、tuple、set 等] 或使用for 循环) .

    生成器惰性的主要优点是内存消耗。他们不需要将所有元素存储在内存中,而只需要存储当前(或者我应该说是下一个)元素。

    【讨论】:

      【解决方案3】:

      生成器表达式是惰性求值的,因此当您返回生成器对象时,代码x for x in a if x &gt;= 4 尚未执行。

      for-in 循环在内部为该生成器对象的循环的每次迭代调用内置的next() 函数。 next() 调用实际上会评估代码,并且该代码指向更新后的 list,它具有您在创建生成器对象后添加的新值集。

      >>> lg = ( x for x in a if x >= 4)
      #evaluates the code and returns the first value
      >>> next(lg) 
      4
      >>> next(lg)
      5
      # if new values are added here to the list 
      # the generator will return them
      

      但在列表解析的情况下,生成器对象的 next() 方法会立即被调用,并且所有值都使用开始时存在的值添加到列表容器中。

      内置的list()[]iterable 对象作为参数,并使用从可迭代对象返回的值构造一个列表。当您将可迭代对象(在您的情况下是可迭代的生成器对象)传递给列表构造函数时,会立即发生这种情况。

      但另一方面,如果你只是简单地执行生成器表达式,你只会得到生成器对象,它既是一个可迭代对象,也是一个迭代器。因此,要么您需要在其上调用 next() 以执行代码并获取值,要么在隐式执行的 for in iterable: 循环中使用它。

      但是请记住,一旦您通过获取 StopIteration 异常耗尽了生成器对象,并且您在列表中添加了一个新值,该值将不会从 next() 调用返回,因为生成器对象只能使用一次.

      >>> a = [2, 3, 4, 5]
      >>> lg = ( x for x in a if x >= 4)
      >>> next(lg)
      4
      >>> next(lg)
      5
      >>> a.append(9)
      >>> next(lg)
      9
      >>> next(lg)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      StopIteration
      # lg is consumed
      >>> a.append(10)
      >>> next(lg)
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      StopIteration
      

      【讨论】:

        猜你喜欢
        • 2014-05-06
        • 2012-08-25
        • 2023-02-02
        • 1970-01-01
        • 2019-06-12
        • 1970-01-01
        • 2022-01-25
        • 2021-10-01
        • 2010-11-28
        相关资源
        最近更新 更多