Вопрос-Ответ

List comprehension rebinds names even after scope of comprehension. Is this right?

Понимание списка повторно связывает имена даже после области понимания. Это правильно?

Понимание показывает необычное взаимодействие с областью. Это ожидаемое поведение?

x = "original value"
squares = [x**2 for x in range(5)]
print(x) # Prints 4 in Python 2!

Рискую пожаловаться, но это жестокий источник ошибок. Когда я пишу новый код, я просто иногда нахожу очень странные ошибки из-за повторного связывания - даже теперь, когда я знаю, что это проблема. Мне нужно создать правило типа "всегда предварять временные переменные в понимании списка подчеркиванием", но даже это не является надежной защитой.
Тот факт, что существует эта случайная бомба замедленного действия, как бы сводит на нет всю приятную "простоту использования" понимания списков.

Переведено автоматически
Ответ 1

Понимание списка приводит к утечке управляющей переменной цикла в Python 2, но не в Python 3. Вот Гвидо ван Россум (создатель Python), объясняющий историю, стоящую за этим:


Мы также внесли еще одно изменение в Python 3, чтобы улучшить эквивалентность между пониманием списка и выражениями генератора. В Python 2 понимание списка "пропускает" управляющую переменную цикла в окружающую область.:


x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

Это был артефакт оригинала
реализация понимания списков;
это был один из "маленьких грязных
секретов" Python на протяжении многих лет. Это началось как
намеренный компромисс для создания списков
понимание происходит ослепительно быстро, и
хотя это не было обычной ошибкой для
новичков, это определенно задевало людей
иногда. Для выражений generator
мы не смогли этого сделать.
Выражения Generator реализованы
с использованием генераторов, выполнение которых
требует отдельного фрейма выполнения.
Таким образом, выражения generator
(особенно если они повторяются по
короткой последовательности) были менее эффективными
, чем понимание списков.


Однако в Python 3 мы решили исправить "маленький грязный секрет" понимания списков, используя ту же стратегию реализации, что и для выражений генератора. Таким образом, в Python 3 приведенный выше пример (после модификации для использования print (x) :-) будет печатать "до", доказывая, что "x" в понимании списка временно затеняет, но не переопределяет "x" в окружающей области.


Ответ 2

Да, понимание списков "пропускает" свою переменную в Python 2.x, точно так же, как циклы for.

Оглядываясь назад, было признано, что это ошибка, и ее удалось избежать с помощью выражений генератора. РЕДАКТИРОВАТЬ: Как отмечает Мэтт Б. этого также удалось избежать, когда синтаксисы set и dictionary для понимания были перенесены из Python 3.

List comprehensions' behavior had to be left as it is in Python 2, but it's fully fixed in Python 3.

This means that in all of:

list(x for x in a if x>32)
set(x//4 for x in a if x>32) # just another generator exp.
dict((x, x//16) for x in a if x>32) # yet another generator exp.
{x//4 for x in a if x>32} # 2.7+ syntax
{x: x//16 for x in a if x>32} # 2.7+ syntax

the x is always local to the expression while these:

[x for x in a if x>32]
set([x//4 for x in a if x>32]) # just another list comp.
dict([(x, x//16) for x in a if x>32]) # yet another list comp.

in Python 2.x all leak the x variable to the surrounding scope.


UPDATE for Python 3.8: PEP 572 introduced := assignment operator that deliberately leaks out of comprehensions and generator expressions! This leaking was motivated by essentially 2 use cases: capturing a "witness" from early-terminating functions like any() and all():

if any((comment := line).startswith('#') for line in lines):
print("First comment:", comment)
else:
print("There are no comments")

and updating mutable state:

total = 0
partial_sums = [total := total + v for v in values]

See Appendix B for exact scoping. The variable is assigned in closest surrounding def or lambda, unless that function declares it nonlocal or global.

Ответ 3

Yes, assignment occurs there, just like it would in a for loop. No new scope is being created.

This is definitely the expected behavior: on each cycle, the value is bound to the name you specify. For instance,

>>> x=0
>>> a=[1,54,4,2,32,234,5234,]
>>> [x for x in a if x>32]
[54, 234, 5234]
>>> x
5234

Once that's recognized, it seems easy enough to avoid: don't use existing names for the variables within comprehensions.

Ответ 4

Interestingly this doesn't affect dictionary or set comprehensions.

>>> [x for x in range(1, 10)]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> x
9
>>> {x for x in range(1, 5)}
set([1, 2, 3, 4])
>>> x
9
>>> {x:x for x in range(1, 100)}
{1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10, 11: 11, 12: 12, 13: 13, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99}
>>> x
9

However it has been fixed in 3 as noted above.

2023-06-07 18:55 python