【问题标题】:Generator expression uses list assigned after the generator's creation生成器表达式使用生成器创建后分配的列表
【发布时间】:2019-03-28 19:53:19
【问题描述】:

我找到了这个示例,但我不明白为什么它的工作方式无法预测? 我想它必须输出[1, 8, 15][2, 8, 22]

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
array = [2, 8, 22]
print(list(g))


>>>[8]

【问题讨论】:

  • aside: if array.count(x) > 0 => x in array 更智能、更快:)

标签: python python-3.x list expression generator


【解决方案1】:

原因是,在创建时,生成器 (a for b in c if d) 仅评估 c(这有时也使 b 可预测)。但是abd 在消耗时间(每次迭代)进行评估。在这里,它在评估 d (array.count(x) > 0) 时使用封闭范围内 arraycurrent 绑定。

例如,您可以这样做:

g = (x for x in [] if a)

没有提前声明a。但是,您必须确保在使用生成器时存在a

但你不能这样做:

g = (x for x in a if True)

根据要求:

您可以使用通用生成器函数观察相似(但不相同)的模式:

def yielder():
    for x in array:
        if array.count(x) > 0:
            yield x

array = [1, 8, 15]
y = yielder()
array = [2, 8, 22]
list(y)
# [2, 8, 22]

生成器函数不会在消耗之前执行其任何主体。因此,即使是 for 循环标头中的 array 也被绑定得很晚。一个更令人不安的例子发生在我们在迭代期间“切换”array

array = [1, 8, 15]
y = yielder()
next(y)
# 1
array = [3, 7]
next(y)  # still iterating [1, 8, 15], but evaluating condition on [3, 7]
# StopIteration raised

【讨论】:

  • 您能解释一下为什么生成器 expression 的行为似乎与生成器 function def yielder(): \ for x in array: \ if array.count(x) > 0: \ yield x 不同。使用list(yielder) 会耗尽,所以你会得到[1, 8, 15],而list(g) 只会得到[8]
  • @jpp 您不能在函数对象上调用list。但是吹毛求疵 =) 我为此添加了一些解释。
  • 谢谢,非常有帮助。当然list(yielder())是我的意思:)
  • “而且由于生成器不会打开自己的命名空间” - 是的。这就是循环变量不会泄漏到外部范围的原因。它不会急切地从创建它的命名空间中复制绑定;它在使用时查找闭包变量。
  • @user2357112 感谢您的评论。我更新了那个部分。根据我在 Python 中找到的大多数关于闭包的文档,我不确定生成器表达式是否真的包含严格意义上的闭包,因为没有嵌套函数。
【解决方案2】:

困惑,答案也是如此,在于:g = (x for x in array if array.count(x) > 0)
如果我们简化这一行,那么它将变成:g = (x for x in array1 if array2.count(x) > 0)

现在,当 generator 创建时,它会保留 array1 对象的引用。因此,即使我将array1 的值更改为任何其他值(即将其设置为新的数组对象),它也不会影响生成器array1 副本。因为只有array1 正在更改它的对象引用。但是array2 是动态检查的。所以如果我们改变它的值,它就会被反映出来。

你可以从下面的代码中看到输出来理解它。见working online here

array1 = [1, 8, 15] #Set value of `array1`
array2 = [2, 3, 4, 5, 8] #Set value of `array2`
print("Old `array1` object ID: " + repr(id(array1)))
print("Old `array2` object ID: " + repr(id(array2)))
g = (x for x in array1 if array2.count(x) > 0)
array1 = [0, 9] #Changed value of `array1`
array2 = [2, 8, 22, 1] #Changed value of `array2`
print("New `array1` object ID: " + repr(id(array1)))
print("New `array2` object ID: " + repr(id(array2)))
print(list(g))

输出:

Old `array1` object ID: 47770072262024
Old `array2` object ID: 47770072263816
New `array1` object ID: 47770072263944
New `array2` object ID: 47770072264008
[1, 8]

【讨论】:

  • 您在这里使用“复制”一词的方式非常具有误导性。生成器表达式不复制任何内容。它只是包含对array 原始值的引用。
【解决方案3】:

来自Generator expressions 上的文档:

生成器表达式中使用的变量惰性求值 为生成器对象调用__next__() 方法(在同一 像普通发电机一样时尚)。然而,可迭代的表达式 最左边的for 子句被立即评估,因此出现错误 由它产生的将在生成器的位置发出 表达式被定义,而不是在第一个值的位置 已检索。

所以当你运行时

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)

仅计算生成器表达式中的第一个 arrayxarray.count(x) 只会在您调用 next(g) 时进行评估。由于您使 array 指向另一个列表 [2, 8, 22] 使用生成器之前,您会得到“意外”的结果。

array = [2, 8, 22]
print(list(g))  # [8]

【讨论】:

    【解决方案4】:

    当您第一次创建数组并分配其中的元素时,数组的元素会指向某个内存位置,并且生成器会保留该位置(而不是数组的)以供其执行。

    但是当你修改它的数组元素时,它会被改变,但是因为 '8' 对它们来说都很常见,python 不会重新分配它并在修改后指向同一个元素。

    看下面的例子更好地理解

    array = [1, 8, 15]
    for i in array:
        print(id(i))
    
    g = (x for x in array if array.count(x) > 0)
    
    print('<======>')
    
    array = [2, 8, 22]
    for i in array:
        print(id(i))
    
    print(array)
    print(list(g))
    

    输出

    140208067495680
    140208067495904
    140208067496128
    <======>
    140208067495712
    140208067495904 # memory location is still same
    140208067496352
    [2, 8, 22]
    [8]
    

    【讨论】:

      【解决方案5】:

      其实仔细看也不是很疯狂。 看看

      g = (x for x in array if array.count(x) > 0)
      

      它将创建一个查看数组的生成器,并搜索现有值的计数是否大于零。所以您的生成器只查找1815,当您将值更改为另一个值时,生成器只会再次查找以前的值而不是新值。因为它(生成器)在数组有它们时创建。

      因此,如果您将数千个值放入数组中,它只会查找这三个值。

      【讨论】:

      • 我不清楚这个答案是说条件还是 array 被立即评估
      猜你喜欢
      • 2018-12-17
      • 1970-01-01
      • 2020-09-06
      • 2023-03-09
      • 1970-01-01
      • 2018-09-01
      • 2017-12-08
      • 2016-04-27
      • 1970-01-01
      相关资源
      最近更新 更多