Зачем понимать список в переменной цикла, но генераторы этого не делают?

Если я что-то делаю со списком, он записывает локальную переменную:

i = 0
test = any([i == 2 for i in xrange(10)])
print i

Отпечатает "9". Однако, если я использую генератор, он не пишет локальную переменную:

i = 0
test = any(i == 2 for i in xrange(10))
print i

Отпечатает "0".

Есть ли веская причина для этой разницы? Является ли это конструктивным решением или просто случайным побочным продуктом того, как реализуются генераторы и списки? Лично мне показалось бы лучше, если бы в контекстах списков не записывались локальные переменные.

Ответ 1

Создатель Pythons, Гвидо ван Россум, упоминает об этом, когда писал о которые были равномерно встроены в Python 3: (внимание мое)

Мы также сделали еще одно изменение в 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" в окружающем сфера.

Итак, в Python 3 вы больше не увидите этого.

Интересно, что ошибки в Python 2 тоже не делают этого; это главным образом из-за того, что ошибки dict были переданы из Python 3 и как таковые уже имели это исправление.

Есть и другие вопросы, которые также охватывают эту тему, но я уверен, что вы уже видели их, когда искали эту тему, не так ли?;)

Ответ 2

Как PEP 289 (Generator Expressions) объясняет:

Переменная цикла (если она является простой переменной или кортежем простых переменных) не подвергается окружающей функции. Это облегчает реализацию и делает типичные случаи использования более надежными.

Похоже, что это было сделано по причинам реализации.

Лично мне показалось бы лучше, если бы в контекстах списков не записывались локальные переменные.

PEP 289 также разъясняет это:

Сопоставление списков также "утечки" их переменной цикла в окружающую область. Это также изменится в Python 3.0, так что семантическое определение понимания списка в Python 3.0 будет эквивалентно списку().

Другими словами, поведение, которое вы описываете, действительно отличается в Python 2, но оно исправлено в Python 3.

Ответ 3

Лично мне показалось бы лучше, если бы в контекстах списков не записывались локальные переменные.

Вы правы. Это исправлено в Python 3.x. Поведение не изменяется в 2.x, так что оно не влияет на существующий код, который (ab) использует это отверстие.

Ответ 4

Потому что... потому что.

Нет, действительно, это. Quirk реализации. И, возможно, ошибка, поскольку она исправлена ​​в Python 3.

Ответ 5

Одно из тонких последствий грязной тайны, описанной выше, заключается в том, что list(...) и [...] не имеют одинаковых побочных эффектов в Python 2:

In [1]: a = 'Before'
In [2]: list(a for a in range(5))
In [3]: a
Out[3]: 'Before'

Так нет побочного эффекта для выражения генератора внутри list-constructor, но побочный эффект присутствует в прямом понимании списка:

In [4]: [a for a in range(5)]
In [5]: a
Out[5]: 4

Ответ 6

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

В Python 2 взгляните на байт-код, сгенерированный для простого понимания списка:

>>> s = compile('[i for i in [1, 2, 3]]', '', 'exec')
>>> dis(s)
  1           0 BUILD_LIST               0
              3 LOAD_CONST               0 (1)
              6 LOAD_CONST               1 (2)
              9 LOAD_CONST               2 (3)
             12 BUILD_LIST               3
             15 GET_ITER            
        >>   16 FOR_ITER                12 (to 31)
             19 STORE_NAME               0 (i)
             22 LOAD_NAME                0 (i)
             25 LIST_APPEND              2
             28 JUMP_ABSOLUTE           16
        >>   31 POP_TOP             
             32 LOAD_CONST               3 (None)
             35 RETURN_VALUE  

он по существу переводит на простой for-loop, что для него используется синтаксический сахар. В результате применяются те же семантики, что и для for-loops:

a = []
for i in [1, 2, 3]
    a.append(i)
print(i) # 3 leaky

В случае с пониманием списка (C) Python использует "скрытое имя списка" и специальную команду LIST_APPEND для обработки создания, но на самом деле ничего не делает.

Итак, ваш вопрос должен обобщать на то, почему Python записывает переменную цикла for в for-loop s; это приятно ответить в блоге от Эли Бендерски.

Python 3, как уже упоминалось, и другими, изменил семантику понимания списка, чтобы лучше соответствовать структуре генераторов (путем создания отдельного кодового объекта для понимания) и по существу является синтаксическим сахаром для следующего:

a = [i for i in [1, 2, 3]]

# equivalent to
def __f(it):
    _ = []
    for i in it
        _.append(i)
    return _
a = __f([1, 2, 3])

это не будет протекать, потому что он не запускается в самой верхней области, как это делает эквивалент Python 2. i просачивается только в __f, а затем уничтожается как локальная переменная для этой функции.

Если вы хотите, посмотрите на байт-код, сгенерированный для Python 3, на dis('a = [i for i in [1, 2, 3]]'). Вы увидите, как загружается "скрытый" кодовый объект, а затем завершается вызов функции.