Почему я могу использовать одно и то же имя для итератора и последовательности в цикле Python?
Это скорее концептуальный вопрос. Недавно я увидел фрагмент кода в Python (он работал в версии 2.7, и он также мог быть запущен в версии 2.5), в котором цикл for
использовал одно и то же имя для списка, который был переименован, и элемента в списке, который поражает меня как плохую практику, так и то, что не должно работать вообще.
Например:
x = [1,2,3,4,5]
for x in x:
print x
print x
Урожайность:
1
2
3
4
5
5
Теперь для меня имеет смысл, что последнее значение будет последним значением, назначенным x из цикла, но я не понимаю, почему вы сможете использовать одно и то же имя переменной для обеих частей for
и выполняйте функцию по назначению. Являются ли они в разных областях? Что происходит под капотом, что позволяет что-то подобное работать?
Ответы
Ответ 1
Что сообщает dis
:
Python 3.4.1 (default, May 19 2014, 13:10:29)
[GCC 4.2.1 Compatible Apple LLVM 5.1 (clang-503.0.40)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from dis import dis
>>> dis("""x = [1,2,3,4,5]
... for x in x:
... print(x)
... print(x)""")
1 0 LOAD_CONST 0 (1)
3 LOAD_CONST 1 (2)
6 LOAD_CONST 2 (3)
9 LOAD_CONST 3 (4)
12 LOAD_CONST 4 (5)
15 BUILD_LIST 5
18 STORE_NAME 0 (x)
2 21 SETUP_LOOP 24 (to 48)
24 LOAD_NAME 0 (x)
27 GET_ITER
>> 28 FOR_ITER 16 (to 47)
31 STORE_NAME 0 (x)
3 34 LOAD_NAME 1 (print)
37 LOAD_NAME 0 (x)
40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
43 POP_TOP
44 JUMP_ABSOLUTE 28
>> 47 POP_BLOCK
4 >> 48 LOAD_NAME 1 (print)
51 LOAD_NAME 0 (x)
54 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
57 POP_TOP
58 LOAD_CONST 5 (None)
61 RETURN_VALUE
Ключевыми битами являются разделы 2 и 3 - мы выставляем значение из x
(24 LOAD_NAME 0 (x)
), а затем получаем его итератор (27 GET_ITER
) и начинаем итерацию по нему (28 FOR_ITER
). Python никогда не возвращается, чтобы снова загрузить итератор.
Кроме того: не имеет смысла делать это, поскольку у него уже есть итератор, и как Абхиджит указывает в своем ответе, раздел 7.3 спецификации Python действительно требует такого поведения).
Когда имя x
переписывается, чтобы указать на каждое значение внутри списка, ранее известного как x
Python не имеет проблем с поиском итератора, потому что ему никогда не нужно снова искать имя x
завершите протокол итерации.
Ответ 2
Использование кода примера в качестве базовой ссылки
x = [1,2,3,4,5]
for x in x:
print x
print x
Я хочу, чтобы вы отсылали раздел 7.3. Инструкция for в руководстве
Выдержка 1
Список выражений оценивается один раз; он должен давать итерабельную объект. Итератор создается для результата expression_list.
Это означает, что ваша переменная x
, которая является символическим именем объекта list
: [1,2,3,4,5]
, вычисляется для итерируемого объекта. Даже если переменная, символическая ссылка изменяет свою принадлежность, поскольку список выражений не оценивается снова, нет никакого влияния на итерируемый объект, который уже был оценен и сгенерирован.
Примечание
- Все в Python - это Object, имеет идентификатор, атрибуты и методы.
- Переменные - это символическое имя, ссылка на один и только один объект в любом конкретном экземпляре.
- Переменные во время выполнения могут изменить свою принадлежность, то есть могут ссылаться на какой-то другой объект.
Выдержка 2
Затем пакет выполняется один раз для каждого элемента, предоставленного итератора в порядке возрастания индексов.
Здесь набор относится к итератору, а не к списку выражений. Таким образом, для каждой итерации итератор выполняется, чтобы получить следующий элемент вместо обращения к исходному списку-выражению.
Ответ 3
Для этого нужно работать таким образом, если вы об этом подумаете. Выражение для последовательности цикла for
может быть любым:
binaryfile = open("file", "rb")
for byte in binaryfile.read(5):
...
Мы не можем запросить последовательность на каждом проходе через цикл, или здесь мы закончим чтение из следующей партии по 5 байт во второй раз. Естественно, Python должен каким-то образом сохранить результат выражения в частном порядке до начала цикла.
Являются ли они в разных областях?
Нет. Чтобы подтвердить это, вы можете сохранить ссылку на исходный словарь (locals()) и заметить, что вы фактически используете одни и те же переменные внутри цикл:
x = [1,2,3,4,5]
loc = locals()
for x in x:
print locals() is loc # True
print loc["x"] # 1
break
Что происходит под капотом, что позволяет что-то вроде этого работать?
Шон Виейра показал, что происходит под капотом, но чтобы описать его в более читаемом коде python, ваш цикл for
по существу эквивалентен этому while
:
it = iter(x)
while True:
try:
x = it.next()
except StopIteration:
break
print x
Это отличается от традиционного подхода индексирования к итерации, который вы видели в более старых версиях Java, например:
for (int index = 0; index < x.length; index++) {
x = x[index];
...
}
Этот подход потерпит неудачу, когда переменная item и переменная последовательности будут одинаковыми, так как последовательность x
больше не будет доступна для поиска следующего индекса после того, как первый раз x
был переназначен в первый элемент.
Однако при первом подходе первая строка (it = iter(x)
) запрашивает объект iterator, который фактически отвечает за предоставление следующий пункт с этого момента. Последовательность, в которой x
изначально указывала, что больше не нужно обращаться напрямую.
Ответ 4
Это разница между переменной (x) и объектом, на которую она указывает (список). Когда цикл for начинается, Python захватывает внутреннюю ссылку на объект, на который указывает x. Он использует объект, а не тот, к которому х ссылается в любой момент времени.
Если вы переназначите x, цикл for не изменится. Если x указывает на изменяемый объект (например, список), и вы можете изменить этот объект (например, удалить элемент), результаты могут быть непредсказуемыми.
Ответ 5
В основном цикл for принимает в списке x
, а затем, сохраняя это как временную переменную, re присваивает x
каждому значению во временной переменной. Таким образом, x
теперь является последним значением в списке.
>>> x = [1, 2, 3]
>>> [x for x in x]
[1, 2, 3]
>>> x
3
>>>
Также как в этом:
>>> def foo(bar):
... return bar
...
>>> x = [1, 2, 3]
>>> for x in foo(x):
... print x
...
1
2
3
>>>
В этом примере x
сохраняется в foo()
как bar
, поэтому, хотя x
переназначается, он все еще существует (ed) в foo()
, чтобы мы могли использовать его для запуска нашей for
.
Ответ 6
x
больше не относится к исходному списку x
, и поэтому нет путаницы. В принципе, python помнит, как он перебирает исходный список x
, но как только вы начнете назначать итерационное значение (0,1,2 и т.д.) На имя x
, оно больше не относится к оригиналу x
список. Имя присваивается значению итерации.
In [1]: x = range(5)
In [2]: x
Out[2]: [0, 1, 2, 3, 4]
In [3]: id(x)
Out[3]: 4371091680
In [4]: for x in x:
...: print id(x), x
...:
140470424504688 0
140470424504664 1
140470424504640 2
140470424504616 3
140470424504592 4
In [5]: id(x)
Out[5]: 140470424504592