Понимание списка связывает имена даже после объема понимания.Это правильно? - PullRequest
111 голосов
/ 17 ноября 2010

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

У меня есть метод:

def leave_room(self, uid):
  u = self.user_by_id(uid)
  r = self.rooms[u.rid]

  other_uids = [ouid for ouid in r.users_by_id.keys() if ouid != u.uid]
  other_us = [self.user_by_id(uid) for uid in other_uids]

  r.remove_user(uid) # OOPS! uid has been re-bound by the list comprehension above

  # Interestingly, it's rebound to the last uid in the list, so the error only shows
  # up when len > 1

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

Тот факт, что эта случайная бомба замедленного действия является своего рода, сводит на нет все приятные «простоты использования» списочных представлений.

Ответы [ 5 ]

159 голосов
/ 17 ноября 2010

В списках есть утечка управляющей переменной цикла в 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 3 мы решили исправить "грязный маленький секрет" списка понимание с помощью того же Стратегия реализации как для генератор выражений. Таким образом, в Python 3, приведенный выше пример (после модификация для использования print (x) :-) выведите «before», доказав, что «x» в списке понимания временно тени, но не перекрывают «х» в окружающем объеме.

47 голосов
/ 17 ноября 2010

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

В ретроспективе это было признано ошибкой, и этого удалось избежать с помощью выражений генератора. РЕДАКТИРОВАТЬ: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *}

Поведение списочных представлений нужно было оставить как есть в Python 2, но оно полностью исправлено в Python 3.

Это означает, что во всех:

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

x всегда локально для выражения, в то время как они:

[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.

в Python 2.x все утечки переменной x в окружающую область.


ОБНОВЛЕНИЕ для Python 3.8 (?) : PEP 572 введет := оператор присваивания, который намеренно пропускает из выражений и выражений генератора! Это мотивировано, по сути, двумя вариантами использования: захват «свидетеля» из функций с ранним завершением, таких как any() и all():

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

и обновление изменяемого состояния:

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

См. Приложение B для точного определения объема. Переменная присваивается в ближайшем окружении def или lambda, если только эта функция не объявляет ее nonlocal или global.

7 голосов
/ 17 ноября 2010

Да, там происходит присвоение, как в цикле for. Новая область не создается.

Это определенно ожидаемое поведение: в каждом цикле значение привязывается к указанному вами имени. Например,

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

Как только это распознается, кажется, что этого легко избежать: не используйте существующие имена для переменных в пределах понимания.

2 голосов
/ 30 ноября 2015

Интересно, что это не влияет на словарь или набор пониманий.

>>> [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

Однако это было исправлено в 3, как отмечено выше.

1 голос
/ 15 ноября 2017

некоторый обходной путь, для Python 2.6, когда это поведение нежелательно

# python
Python 2.6.6 (r266:84292, Aug  9 2016, 06:11:56)
Type "help", "copyright", "credits" or "license" for more information.
>>> x=0
>>> a=list(x for x in xrange(9))
>>> x
0
>>> a=[x for x in xrange(9)]
>>> x
8
...