Как избежать утечки выражений присваивания Python в пониманиях
В книге " Эффективный 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
Что мне не хватает? Есть ли способ вообще избежать утечки выражений присваивания в пониманиях?
1 ответ
В Python невозможно не допустить утечки переменных цикла
В отличие от других языков, таких как C или Java, Python не имеет отдельной области видимости внутри
if
и
for
блоки. Итак, когда вы используете
:=
оператор в
if
заявление,
for
цикл или понимание списка, назначенная переменная будет в области видимости на протяжении всей оставшейся части определения функции или класса. Это также означает, что после каждого
for
цикла, переменная цикла все еще будет в области видимости и будет содержать значение последней итерации цикла.
Я не согласен с автором " Эффективного 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
Это исключение, вероятно, является причиной вашего замешательства. Это только особый случай и не применим при использовании
:=
внутри понимания списка.