发电机工作异常。

4
a = [1, 2, 3]
b = (c for c in a if c in a)
a = [2, 3, 4]
print(list(b))


def d(expr):
    for c in expr:
        if c in expr:
            yield c


a1 = [1,2,3]
t = d(a1)
a1 = [2,3,4]
print(list(t))

输出:

[2, 3]
[1, 2, 3]

问题: 1)在第一个变体中,循环保留了列表的旧值([1,2,3]),但是条件表达式使用新列表a([2,3,4])。 2)如何用自己的生成器得到与生成表达式不同的结果?

例如,我将第一个示例中的a替换为实际列表,希望这能更清楚地回答我的第一个问题。

a = [1, 2, 3]
b = (c for c in [1,2,3] if c in [2, 3, 4])
a = [2, 3, 4]
print(list(b))

输出:
[2,3]

1
函数内所有对 a1 的引用都指向局部变量 a1。在全局范围内更改 a1 不会对其产生任何影响。 - glibdud
@glibdud 真的吗?t1 = [1,2,3,4] def r(t1): t1[0] = 10 r(t1) print(t1) - 我明白了,列表发生了变化。 - rtut
3
这两件事不一样。你评论中的例子是在函数内改变对象以从全局范围进行更改。问题中的例子只是在全局作用域中重新绑定 a1,对函数中的 a1 局部变量没有影响。 - glibdud
2
一个更合适的比较可能是将 a1 = [2,3,4] 改为 a1[:] = [2,3,4],这将产生另一个(不同的)输出。 - glibdud
尝试使用 a1[:] = [2, 3, 4] 替代。这将给你 [2, 3, 4],因为当执行到 for c in a1 时,a1 已经是 [2, 3, 4] - Patrick Haugh
我认为你的编辑只会让它更加混乱。老实说,我会删除第四行之后的所有代码,因为那里是出现问题的地方。 - glibdud
1个回答

3
根据PEP 289,我们可以将您的生成器表达式重写为以下形式:
# b = (c for c in a if c in a)

def __gen(expr):
    for c in expr:
        if c in a:
            yield c
b = __gen(iter(a))

在这里,对于for循环中使用的[1, 2, 3]的引用被立即捕获(当你创建b时),但是if c in a检查使用新的全局a,它等于[2, 3, 4]
另一方面,当你编写一个生成器函数时,a1被传递给函数并存储以供稍后在forif x in y表达式中使用。赋值a1 = [2, 3, 4]没有任何影响。
要使函数执行与生成器表达式相同的操作,你只需要将if c in expr替换为if c in a1

为什么你的代码中使用了 "for c in expr:" 但是却用了 "if c in a:"?我改变了自己的代码表达方式以提高可读性。 - rtut
1
@rtut 如果你想知道Norrius为什么这样做,那是因为链接的PEP规定应该这样做。如果你想知道Python为什么以这种方式实现,那也在PEP中有说明(https://www.python.org/dev/peps/pep-0289/#early-binding-versus-late-binding)。 - glibdud
def d(expr): for c in expr: if c in expr: yield ca1 = [1,2,3] t = d(a1) a1 = [2,3,4] print(list(t)) 这不是新的全局变量吗?但代码返回相同的结果?我想知道为什么循环会保存旧列表?如果使用新列表会怎样? - rtut
如果您将代码中expr的最后一个出现改为 a1,则它将等同于Norrius在这里向您展示的内容,并且会给您提供您正在寻找的答案。 - glibdud
@glibdud编辑了答案并包含了这一点。 - Norrius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接