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

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

У меня есть способ:

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

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

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


person Jabavu Adams    schedule 16.11.2010    source источник
comment
-1: жестокий источник ошибок? Едва ли. Почему выбрали такой аргументированный термин? Как правило, самые дорогостоящие ошибки - это недопонимание требований и простые логические ошибки. Ошибки такого рода были стандартной проблемой для многих языков программирования. Зачем называть это «жестоким»?   -  person S.Lott    schedule 17.11.2010
comment
Это нарушает принцип наименьшего удивления. Это также не упоминается в документации python по пониманию списков, но, тем не менее, несколько раз упоминается, насколько они просты и удобны. По сути, это мина, которая существовала вне моей языковой модели, и поэтому я не мог ее предвидеть.   -  person Jabavu Adams    schedule 18.11.2010
comment
+1 за грубый источник ошибок. Слово «жестокий» полностью оправдано.   -  person Nathaniel    schedule 20.02.2013
comment
Единственная жестокая вещь, которую я здесь вижу, - это ваше соглашение об именах. Это уже не 80-е, вы не ограничены трехсимвольными именами переменных.   -  person UloPe    schedule 02.06.2014
comment
Примечание: в документации действительно указано, что понимание списка эквивалентно явной конструкции for-loop и for-loops переменных утечки. Так что это не было явным, но было заявлено неявно.   -  person Bakuriu    schedule 04.07.2015
comment
@Bakuriu Явное лучше, чем неявное.   -  person 0xc0de    schedule 24.05.2017


Ответы (6)


Понимание списков приводит к утечке переменной управления циклом в 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 в течение многих лет. Это началось как преднамеренный компромисс, чтобы сделать понимание списка ослепительно быстрым, и хотя это не было распространенной ловушкой для новичков, это определенно иногда ужалило людей. Для выражений генератора мы не могли этого сделать. Выражения генератора реализуются с помощью генераторов, для выполнения которых требуется отдельный фрейм исполнения. Таким образом, выражения генератора (особенно если они повторяются в короткой последовательности) были менее эффективны, чем понимание списков.

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

person Steven Rumbalski    schedule 16.11.2010
comment
Я добавлю, что, хотя Гвидо называет это маленьким грязным секретом, многие считали это функцией, а не ошибкой. - person Steven Rumbalski; 11.11.2011
comment
Также обратите внимание, что теперь в 2.7 у интерпретаций множеств и словарей (и генераторов) есть частные области видимости, но у понимания списков по-прежнему нет. Хотя в этом есть некоторый смысл, поскольку все первые были перенесены из Python 3, это действительно резко контрастирует с пониманием списков. - person mbauman; 28.11.2011
comment
Я знаю, что это безумно старый вопрос, но почему некоторые сочли это особенностью языка? Есть ли что-нибудь в пользу утечки такой переменной? - person Mathias Müller; 28.08.2015
comment
тьфу только что укусил это ... утечка, это определенно было сюрпризом ИМО. Думаю, это уже не актуально, но почувствовал необходимость упомянуть: P - person JPC; 16.12.2015
comment
Утечки for: loops есть веские причины, особенно для доступа к последнему значению после раннего break - но не имеет отношения к пониманию. Я вспоминаю некоторые обсуждения comp.lang.python, когда люди хотели назначать переменные в середине выражения. Был найден менее безумный способ использования предложений с одним значением, например. sum100 = [s for s in [0] for i in range(1, 101) for s in [s + i]][-1], но ему просто нужна переменная, локальная для понимания, и она так же хорошо работает в Python 3. Я думаю, утечка была единственным способом сделать переменную видимой вне выражения. Все согласились, что эти методы ужасны :-) - person Beni Cherniavsky-Paskin; 11.08.2016
comment
@Mathias, Утечка дала пониманию списка те же правила области видимости, что и вложенные / лямбда-функции, которые также могут обращаться к переменным в своей области определения, и, таким образом, сделали выражения списков столь же мощными. - person Riaz Rizvi; 15.02.2017
comment
Проблема здесь не в доступе к окружающей области понимания списка, а в привязке к области понимания списка, влияющей на окружающую область. - person Felipe Gonçalves Marques; 28.09.2018

Да, понимание списков «пропускает» свою переменную в Python 2.x, как и в циклах for.

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

Поведение списков должно быть таким же, как в 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.

person Beni Cherniavsky-Paskin    schedule 16.11.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

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

person JAL    schedule 16.11.2010

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

>>> [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, как указано выше.

person Chris Travers    schedule 30.11.2015
comment
Этот синтаксис вообще не работает в Python 2.6. Вы говорите о Python 2.7? - person Paul Hollingsworth; 20.01.2017
comment
Python 2.6 имеет только понимание списков, как и Python 3.0. 3.1 добавил набор и словарь, и они были перенесены в 2.7. Извините, если это было непонятно. Он должен был указать на ограничение другого ответа, и к каким версиям он применяется, не совсем однозначно. - person Chris Travers; 20.01.2017
comment
Хотя я могу представить аргумент, что есть случаи, когда использование python 2.7 для нового кода имеет смысл, я не могу сказать то же самое о python 2.6 ... Даже если 2.6 - это то, что идет с вашей ОС, вы не застряли с Это. Подумайте об установке virtualenv и использовании 3.6 для нового кода! - person Alex L; 01.02.2017
comment
Однако вопрос о Python 2.6 может возникнуть при поддержке существующих устаревших систем. Так что как историческая справка это не совсем неуместно. То же самое с 3.0 (ick) - person Chris Travers; 02.02.2017
comment
Извините, если я показался грубым, но это никак не отвечает на вопрос. Это лучше подходит в качестве комментария. - person 0xc0de; 24.05.2017
comment
Как бы это отформатировать как комментарий? Вы бы предпочли, чтобы я составил сообщение в блоге и ссылку на него в комментарии? Прости. Я не хочу показаться грубым, но я пытаюсь понять практический компромисс здесь и то, как именно вы бы предпочли, чтобы это выглядело. - person Chris Travers; 24.05.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
person Marek Slebodnik    schedule 15.11.2017

В python3, находясь в понимании списка, переменная не изменяется после выхода из области видимости, но когда мы используем простой цикл for, переменная переназначается вне области видимости.

i = 1 print (i) print ([i in range (5)]) print (i) Значение i останется только 1.

Теперь просто используйте цикл for, значение i будет переназначено.

person ASHOK KUMAR    schedule 22.04.2020