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