Почему я могу использовать одно и то же имя для итератора и последовательности в цикле Python for?
это скорее концептуальный вопрос. Недавно я видел кусок кода в Python (он работал в 2.7, и он также мог быть запущен в 2.5), в котором a for
loop использовал одно и то же имя как для списка, который повторялся, так и для элемента в списке, что поражает меня как плохой практикой, так и тем, что вообще не должно работать.
например:
x = [1,2,3,4,5]
for x in x:
print x
print x
выходы:
1
2
3
4
5
5
теперь, это имеет смысл для меня, что последнее значение печатается будет последнее значение, присвоенное x из цикла, но я не понимаю, почему вы могли бы использовать одно и то же имя переменной для обеих ваших частей for
цикл и иметь его функцию по назначению. Они находятся в разных областях? Что происходит под капотом, что позволяет что-то вроде этой работы?
6 ответов:
что значит
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)
) и тогда мы получим его iterator (27 GET_ITER
) и начать перебирать его (28 FOR_ITER
). Питон никогда не возвращается, чтобы снова загрузить итератор.в сторону: это не имело бы никакого смысла делать, так как он уже имеет итератор, и как Абхиджит указывает в своем ответе,7.3 из Спецификация Python на самом деле требует такого поведения).
когда имя
x
перезаписывается, чтобы указать на каждое значение внутри списка, ранее известного какx
Python не имеет никаких проблем с поиском итератора, потому что ему никогда не нужно смотреть на имяx
снова завершить протокол итерации.
используя Ваш пример кода в качестве основной ссылки
x = [1,2,3,4,5] for x in x: print x print x
Я хотел бы, чтобы вы ссылались на раздел 7.3. The for statement в руководстве
выдержка 1
список выражений вычисляется один раз; он должен давать итерационный объект. Итератор создается для результата expression_list.
это означает, что переменная
x
, что является символическим именем объектаlist
:[1,2,3,4,5]
вычисляется для итерационного объекта. Даже если переменная, символическая ссылка изменяет свою верность, как expression-list не оценивается снова, нет никакого влияния на итерационный объект, который уже был оценен и сгенерирован.Примечание
- все в Python является объектом, имеет идентификатор, атрибуты и методы.
- переменные-это символьное имя, ссылка на один и только один объект в каждом конкретном случае.
- переменные во время выполнения могут изменять свою верность, т. е. могут ссылаться на какой-либо другой объект.
Отрывок 2
затем набор выполняется один раз для каждого элемента, предоставленного итератор, в порядке возрастания индексов.
ЗДЕСЬ Набор ссылается на итератор, а не на список выражений. Таким образом, для каждой итерации итератора для получения следующий пункт вместо ссылки на исходное выражение-список.
это необходимо для того, чтобы работать таким образом, если вы думаете об этом. Выражение для последовательности
for
петли может быть что угодно:binaryfile = open("file", "rb") for byte in binaryfile.read(5): ...
мы не можем запрашивать последовательность на каждом проходе через цикл, или здесь мы закончим чтение из далее пакет из 5 байт второй раз. Естественно, Python должен каким-то образом хранить результат выражения в частном порядке до начала цикла.
они в разных области?
нет. Чтобы подтвердить это, вы можете сохранить ссылку на исходный словарь scope (местные жители()) и обратите внимание, что вы на самом деле используете те же переменные внутри цикла:
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]; ... }
этот подход потерпит неудачу, когда переменная элемента и переменная последовательности одинаковы, потому что последовательность
x
больше не будет доступен для поиска следующего индекса после первого разаx
был переведен в первый пункт.при первом подходе, однако, первая строка (
it = iter(x)
) просит итератор объекта что на самом деле отвечает за предоставление следующего пункта с тех пор. Последовательность, котораяx
первоначально указанная больше не нуждается в прямом доступе.
это разница между переменной (x) и объектом, на который она указывает (список). Когда цикл for запускается, Python захватывает внутреннюю ссылку на объект, на который указывает x. он использует объект, а не то, что x происходит со ссылкой в любой момент времени.
Если вы переназначите x, цикл for не изменится. Если x указывает на изменяемый объект (например, список) и вы изменяете этот объект (например, удаляете элемент), результаты могут быть непредсказуемыми.
в основном, цикл 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
- это было перераспределено, оно до сих пор существует(ЭД) вfoo()
так что мы могли бы использовать его, чтобы вызвать нашиfor
петли.
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