Почему я могу использовать одно и то же имя для итератора и последовательности в цикле 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 77

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