Почему я могу использовать индекс списка как переменную индексации в цикле for?

У меня есть следующий код:

a = [0,1,2,3]

for a[-1] in a:
  print(a[-1])

Выход:

0
1
2
2

Меня смущает, почему индекс списка можно использовать как переменную индексации в цикле for.

Ответ 1

a[-1] индексы, такие как a[-1] в выражении for a[-1] in a, действительны, как указано в target_list грамматики for_stmt (и, в частности, target_list), где slicing является допустимой целью для назначения.

"А? Задание? Какое отношение это имеет к моему выводу?"

В самом деле, это все, что связано с выходом и результатом. Давайте погрузимся в документацию для цикла for-in:

for_stmt ::=  "for" target_list "in" expression_list ":" suite

Список выражений вычисляется один раз; это должно привести к повторяемому объекту. Итератор создается для результата expression_list. Затем набор выполняется один раз для каждого элемента, предоставленного итератором, в порядке, возвращаемом итератором. Каждый элемент, в свою очередь, назначается целевому списку с использованием стандартных правил для назначений (см. Операторы назначения), а затем выполняется набор.

(выделение добавлено)
Обратите внимание, что набор ссылается на оператор под блоком print(a[-1]) в нашем конкретном случае.

Давайте немного повеселимся и расширим оператор печати:

a = [0, 1, 2, 3]
for a[-1] in a:
    print(a, a[-1])

Это дает следующий вывод:

[0, 1, 2, 0] 0    # a[-1] assigned 0
[0, 1, 2, 1] 1    # a[-1] assigned 1
[0, 1, 2, 2] 2    # a[-1] assigned 2
[0, 1, 2, 2] 2    # a[-1] assigned 2 (itself)

(комментарии добавлены)

Здесь, a[-1] изменяется на каждой итерации, и мы видим, что это изменение распространяется на a. Опять же, это возможно из-за того, что slicing является допустимой целью.

Хороший аргумент, выдвинутый Ев. Коунис относится к первому предложению приведенного выше документа: "Список выражений вычисляется один раз". Не означает ли это, что список выражений является статическим и неизменным, постоянным в [0, 1, 2, 3]? Не следует ли, a[-1] назначать 3 на последней итерации?

Ну, Конрад Рудольф утверждает, что:

Нет, [список выражений] вычисляется один раз для создания итерируемого объекта. Но этот итерируемый объект все еще перебирает исходные данные, а не их копию.

(выделение добавлено)

Следующий код демонстрирует, насколько итеративно it лениво возвращает элементы списка x.

x = [1, 2, 3, 4]
it = iter(x)
print(next(it))    # 1
print(next(it))    # 2
print(next(it))    # 3
x[-1] = 0
print(next(it))    # 0

(код, вдохновленный Kounis ')

Если бы оценка была нетерпеливой, мы могли бы ожидать, что x[-1] = 0 окажет нулевое влияние на it и ожидаем, что 4 будет напечатано. Это явно не так и идет, чтобы показать, что по такому же принципу, наш for -loop лениво дает число от a следующие присвоений a[-1] на каждой итерации.

Ответ 2

(Это скорее длинный комментарий, чем ответ - уже есть несколько хороших комментариев, особенно @TrebledJ. Но я должен был думать об этом явно с точки зрения перезаписи переменных, которые уже имеют значения, прежде чем он щелкнул для меня.)

Если у тебя есть

x = 0
l = [1, 2, 3]
for x in l:
    print(x)

Вы не будете удивлены, что x переопределяется каждый раз через цикл. Хотя x существовал и раньше, его значение не используется (т.е. for 0 in l: что приведет к ошибке). Скорее, мы присваиваем значения от l до x.

Когда мы делаем

a = [0, 1, 2, 3]

for a[-1] in a:
  print(a[-1])

даже если a[-1] уже существует и имеет значение, мы не помещаем это значение, а назначаем a[-1] каждый раз в цикле.

Ответ 3

Левое выражение оператора цикла for присваивается каждому элементу в итерируемом справа в каждой итерации, поэтому

for n in a:
    print(n)

это просто модный способ сделать:

for i in range(len(a)):
    n = a[i]
    print(n)

Точно так же,

for a[-1] in a:
  print(a[-1])

это просто модный способ сделать:

for i in range(len(a)):
    a[-1] = a[i]
    print(a[-1])

где в каждой итерации последний элемент a назначается со следующим элементом в a, поэтому, когда итерация наконец достигает последнего элемента, его значение получает последний, назначенный со вторым последним элементом, 2.

Ответ 4

Это интересный вопрос, и вы можете понять его так:

for v in a:
    a[-1] = v
    print(a[-1])

print(a)

на самом деле становится: a [0, 1, 2, 2] после того, как циклы

Выход:

0
1
2
2
[0, 1, 2, 2]

Я надеюсь, что это поможет вам, и прокомментируйте, если у вас есть дополнительные вопросы. :)

Ответ 5

Ответ TrebledJ объясняет техническую причину, почему это возможно.

Зачем тебе это делать?

Предположим, у меня есть алгоритм, который работает с массивом:

x = np.arange(5)

И я хочу протестировать результат алгоритма, используя разные значения первого индекса. Я могу просто пропустить первое значение, восстанавливая массив каждый раз:

for i in range(5):
    print(np.r_[i, x[1:]].sum())

( np.r_)

Это создаст новый массив на каждой итерации, что не идеально, особенно если массив большой. Чтобы повторно использовать одну и ту же память на каждой итерации, я могу переписать ее следующим образом:

for i in range(5):
    x[0] = i
    print(x.sum())

Что, вероятно, яснее, чем первая версия.

Но это точно идентично более компактному способу написать это:

for x[0] in range(5):
    print(x.sum())

все вышеперечисленное приведет к:

10
11
12
13
14

Теперь это тривиальный "алгоритм", но будут более сложные цели, в которых может потребоваться проверить изменение одного (или нескольких, но усложняющих вещи из-за распаковки присваивания) в массиве на ряд значений, предпочтительно без копирование всего массива. В этом случае вы можете использовать индексированное значение в цикле, но будьте готовы запутать любого, кто поддерживает ваш код (включая вас). По этой причине вторая версия, явно назначающая x[0] = i, вероятно, предпочтительнее, но если вы предпочитаете более компактный for x[0] in range(5) стиле for x[0] in range(5), это должен быть допустимый вариант использования.

Ответ 6

a[-1] относится к последнему элементу a, в данном случае a[3]. Цикл for немного необычен тем, что использует этот элемент в качестве переменной цикла. Он не оценивает этот элемент при входе в цикл, а скорее назначает его на каждой итерации цикла.

Итак, сначала a[-1] устанавливается значение 0, затем 1, затем 2. Наконец, на последней итерации цикл for получает a[3] которое в этот момент равно 2, поэтому список заканчивается как [0, 1, 2, 2].

Более типичный цикл for использует простое имя локальной переменной в качестве переменной цикла, например, for x... В этом случае x устанавливается на следующее значение на каждой итерации. Этот случай не отличается, за исключением того, что a[-1] устанавливается на следующее значение при каждой итерации. Вы не видите это очень часто, но это соответствует.

Ответ 7

В Python переменная, используемая в цикле for, не связана с областью действия цикла, как в других языках. Это стандартное назначение.

for x in [1, 2, 3]:
    ...

print(x) # x still exists here, it is 3

В вашем коде строка...

for a[-1] in a:

Итеративно присваивает значения идентификатору a[-1], который является последней позицией в списке.

Это может помочь распечатать весь список на каждом этапе.

a = [0, 1, 2, 3]

for a[-1] in a:
    print(a)

Выход

[0, 1, 2, 0]
[0, 1, 2, 1]
[0, 1, 2, 2]
[0, 1, 2, 2]

Ответ 8

Кодовое интервью из ада: Второй раунд

Каков результат следующего?

a=[1,2,3,4]
for i,a[-i] in enumerate(a):
  print(a, i, a[-i])