Python 生成器理解中的闭包语义

Closure semantics in Python generator comprehensions

在下面的代码中,l1 得到 ([1], [1])l2, l3, l4 得到 ([0], [1])。这让我感到惊讶,尤其是在 in 子句 (l3, l4) 中使用 t 之间的区别,无论是生成器理解还是列表理解,与 if 子句 (l1, l2),它在哪里。

l1 = [(x for x in (0, 1) if x == t) for t in (0, 1)]
l2 = [[x for x in (0, 1) if x == t] for t in (0, 1)]
l3 = [(x for x in [t]) for t in (0, 1)]
l4 = [[x for x in [t]] for t in (0, 1)]
print([(*map(list, l),) for l in (l1, l2, l3, l4)])

能否请您详细解释一下此类表达的规则?一个link相关文档?有道理吗?

当你执行

l1 = [(x for x in (0, 1) if x == t) for t in (0, 1)]

l1 是一个生成器列表,每个生成器都持有对 相同 “捕获”变量 t 的引用。当创建 l1[0] 时,t 的值为 0,但尚未评估生成器。 l1[1]创建时,t的值为1,以后不修改。您可以使用以下方式检查:

c1 = l1[0]
c2 = l1[1]
print(c1.gi_frame.f_locals)
print(c2.gi_frame.f_locals)
print(c1.gi_frame.f_locals['t'] is c2.gi_frame.f_locals['t'])

至于为什么l3没有出现这种情况,我的理解是为了构建 生成器,必须评估它们的限制,因此必须评估 [t] 以及在创建生成器时创建的列表。下面是一个有助于理解发生了什么的修改脚本(原始代码被注释掉以方便比较):

def testequal(x, t):
    print(f"Called with x={x}, t={t}")
    return x == t

def generate_limits():
    print("Creating limits")
    return (0, 1)

def generate_list(t):
    print(f"creating list with t={t}")
    rv = [t,]
    return rv

print("Creating l1...")
# l1 = [(x for x in (0, 1) if x == t) for t in (0, 1)]
l1 = [(x for x in generate_limits() if testequal(x,t)) for t in (0, 1)]
print("Creating l2...")
l2 = [[x for x in (0, 1) if x == t] for t in (0, 1)]
print("Creating l3...")
# l3 = [(x for x in [t]) for t in (0, 1)]
l3 = [(x for x in generate_list(t)) for t in (0, 1)]
print("Creating l3...")
l4 = [[x for x in [t]] for t in (0, 1)]
print("Evaluating..." )
print([(*map(list, l),) for l in (l1, l2, l3, l4)])