Как избежать утечки выражений присваивания 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

Это исключение, вероятно, является причиной вашего замешательства. Это только особый случай и не применим при использовании := внутри понимания списка.

Другие вопросы по тегам