6

Effective Python一书中,作者建议使用赋值表达式来避免理解中的冗余,例如:

def fun(i):
    return 2 * i

result = {x: y for x in [0, 1, 2, 3] if (y := fun(x)) > 3}

代替

result = {x: fun(x) for x in [0, 1, 2, 3] if fun(x) > 3}

result有价值{2: 4, 3: 6}

作者指出

如果推导式在推导式的值部分使用海象运算符并且没有条件,它会将循环变量泄漏到包含范围内。[...] 最好不要泄漏循环变量,所以我建议只在理解的条件部分使用赋值表达式。

但是,在上面的示例中,y在程序结束时设置为 6。因此,赋值表达式中的变量泄漏了,尽管它是在条件中定义的。

列表推导也会发生同样的事情:

>>> _ = [(x, leak) for x in range(4) if (leak := 2 * x) > 3]
>>> leak
6

甚至对于生成器表达式:

>>> it = ((x, leak) for x in range(4) if (leak := 2 * x) > 3)
>>> next(it)
(2, 4)
>>> leak
4
>>> next(it)
(3, 6)
>>> leak
6

我错过了什么?有什么方法可以完全避免在理解中的赋值表达式中泄漏?

4

1 回答 1

0

在 Python 中,不可能泄漏循环变量

if与 C 或 Java 等其他语言不同,Python 在和for块中没有单独的范围。因此,当您在语句、循环或列表推导中使用:=运算符时,分配的变量将在函数或类定义的其余部分的范围内。这也意味着在每次循环之后,循环变量仍将在范围内并包含循环最后一次迭代的值。ifforfor

如果他认为这是一件坏事,我不同意Effective Python的作者。“泄漏”循环变量可能非常有用!考虑以下示例:

while line := f.readLine():
    if 'Kilian' in line:
        break

print('This is the first line that contains your name: ', line)

然而,这条规则有一个例外:在列表推导中进行的隐式赋值有它们自己的范围:

>>> [x for x in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

这个例外可能是你困惑的根源。这只是一种特殊情况,在使用:=内部列表推导时不适用。

于 2020-10-07T15:30:51.753 回答