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)])
在下面的代码中,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)])