and*_*ino 6 python python-assignment-expression
在Effective Python一书中,作者建议使用赋值表达式来避免理解中的冗余,例如:
def fun(i):
return 2 * i
result = {x: y for x in [0, 1, 2, 3] if (y := fun(x)) > 3}
Run Code Online (Sandbox Code Playgroud)
代替
result = {x: fun(x) for x in [0, 1, 2, 3] if fun(x) > 3}
Run Code Online (Sandbox Code Playgroud)
result有价值{2: 4, 3: 6}。
作者指出
如果推导式在推导式的值部分使用海象运算符并且没有条件,它会将循环变量泄漏到包含范围中。[...] 最好不要泄漏循环变量,因此我建议仅在推导式的条件部分使用赋值表达式。
然而,在上面的例子中, y在程序结束时设置为 6。因此,赋值表达式中的变量泄漏了,尽管它是在条件中定义的。
列表推导式也会发生同样的事情:
>>> _ = [(x, leak) for x in range(4) if (leak := 2 * x) > 3]
>>> leak
6
Run Code Online (Sandbox Code Playgroud)
甚至对于生成器表达式:
>>> 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
Run Code Online (Sandbox Code Playgroud)
我错过了什么?有什么方法可以完全避免在推导式中的赋值表达式泄漏?
if与 C 或 Java 等其他语言不同,Python 在和块内没有单独的作用域for。:=因此,当您在if语句、for循环或列表理解中使用运算符时,分配的变量将在函数或类定义的其余部分的范围内。这也意味着在每次for循环之后,循环变量仍将在范围内并包含循环最后一次迭代的值。
如果《Effective Python》的作者认为这是一件坏事,我不同意他的观点。“泄漏”循环变量非常有用!考虑以下示例:
while line := f.readLine():
if 'Kilian' in line:
break
print('This is the first line that contains your name: ', line)
Run Code Online (Sandbox Code Playgroud)
然而,这一规则有一个例外:列表推导式中的隐式赋值有其自己的作用域:
>>> [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
Run Code Online (Sandbox Code Playgroud)
这个例外可能是您感到困惑的根源。:=这只是一种特殊情况,在使用内部列表理解时不适用。