Python 生成器表达式使用在生成器';s的创造

Python 生成器表达式使用在生成器';s的创造,python,expression,generator,Python,Expression,Generator,我发现了这个例子,我不明白为什么它的工作不可预测? 我认为它必须输出[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] 原因是,在创建时,生成器(c中的a表示b,如果d)仅计算c(这有时也使b可预测)。但是a,b,d在消耗时间(每次迭代)进行评估。这里,在计算d(array.

我发现了这个例子,我不明白为什么它的工作不可预测? 我认为它必须输出
[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]

原因是,在创建时,生成器
(c中的a表示b,如果d)
仅计算
c
(这有时也使
b
可预测)。但是
a
b
d
在消耗时间(每次迭代)进行评估。这里,在计算
d
array.count(x)>0
)时,它使用封闭范围中
array
的当前绑定

例如,您可以执行以下操作:

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 = [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

事实上,如果你仔细看的话,这并不是很疯狂。 看

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

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

来自以下文档:

生成器表达式中使用的变量在 为生成器对象调用该方法(在相同的 与普通发电机一样流行)。然而,在 最左边的子句立即求值,因此出现错误 由其产生的气体将在发电机 表达式被定义,而不是在第一个值 已检索

所以当你跑步的时候

array = [1, 8, 15]
g = (x for x in array if array.count(x) > 0)
仅计算生成器表达式中的第一个
数组
x
数组。只有在调用
next(g)
时,才会计算计数(x)
。由于在使用生成器之前,将
数组
指向另一个列表
[2,8,22]
,因此会得到“意外”结果

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

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

但是,当您修改数组的元素时,它会发生更改,但由于“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))
array=[1,8,15]
对于数组中的i:
打印(id(i))
g=(如果array.count(x)>0,则数组中的x代表x)
打印(“”)
数组=[2,8,22]
对于数组中的i:
打印(id(i))
打印(数组)
打印(列表(g))
输出

140208067495680
140208067495904
140208067496128
<======>
140208067495712
140208067495904 # memory location is still same
140208067496352
[2, 8, 22]
[8]
140208067495680
140208067495904
140208067496128
140208067495712
140208067495904#内存位置仍然相同
140208067496352
[2, 8, 22]
[8]

困惑和答案就在这一行:
g=(如果数组中的x为x,则数组中的x.count(x)>0)

如果我们简化这一行,那么它将变成:
g=(如果array2.count(x)>0,那么x代表array1中的x)

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

您可以从下面的代码中看到输出,从而更好地理解它。请看:

输出:

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

旁白:
if-array.count(x)>0
=>
x-in-array
更智能更快:)您能解释一下为什么生成器表达式的行为似乎与生成器函数的行为不同吗
def yielder():\对于数组中的x:\if-array.count(x)>0:\yield x
。使用
list(yielder)
排气,因此您得到
[1,8,15]
,而
list(g)
仅给出
[8]
@jpp,您无法对函数对象调用
list
。但是撇开吹毛求疵不谈,我为此做了一些解释。谢谢你,非常有帮助。当然,
list(yielder())
就是我的意思:)“而且由于生成器不打开自己的名称空间”——是的,它打开了。这就是为什么循环变量不会泄漏到外部范围。它没有做的是急切地从创建绑定的名称空间复制绑定;它在使用时查找闭包变量。@user2357112 Thx用于注释。我更新了那个部分。根据我在Python中找到的关于闭包的大多数文档,我不确定生成器表达式是否真的包含严格意义上的闭包,因为没有嵌套函数。我不清楚这个答案是否表示条件或数组可以通过使用“copy”一词的方式立即计算这是相当误导的。生成器表达式不复制任何内容。它只是保存对
数组的原始值的引用。
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]