Зачем понимать список в переменной цикла, но генераторы этого не делают?
Если я что-то делаю со списком, он записывает локальную переменную:
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]]')
. Вы увидите, как загружается "скрытый" кодовый объект, а затем завершается вызов функции.