为什么在for循环中可以使用列表索引作为索引变量?

时间:2019-04-12 03:45:57

标签: python for-loop indexing

我有以下代码:

a = [0,1,2,3]

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

输出为:

0
1
2
2

我对为什么列表索引可以用作for循环中的索引变量感到困惑。

6 个答案:

答案 0 :(得分:91)

表达式a[-1]中的列表索引,例如for a[-1] in afor_stmt(特别是target_list)语法标记所指定的那样有效,其中slicing是有效的分配目标。

“嗯?分配?那 与我的输出有什么关系?”

实际上,它与输出和结果有关。让我们深入研究documentation for a for-in loop

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

表达式列表只计算一次;它应该产生一个可迭代的对象。为expression_list的结果创建一个迭代器。然后,按照迭代器返回的顺序,对迭代器提供的每个项目执行一次套件。 依次使用标准分配规则将每个项目分配给目标列表 (请参见Assignment statements),然后执行套件。

(添加了重点)
N.B。 套房是指for块下的语句,在我们的特殊情况下为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是有效的目标,因此有可能。

Ev. Kounis提出的一个很好的论点是上面引用的文档的第一句话:“ 表达式列表被计算一次”。这是否意味着表达式列表是静态的并且是不变的,在[0, 1, 2, 3]处保持不变?因此,a[-1]是否应在最后一次迭代中分配给3

好吧,Konrad Rudolph断言:

  

不,[表达式列表]被评估一次以创建一个可迭代对象。但是,该可迭代对象仍然会迭代原始数据,而不是其副本。

(添加了强调)

以下代码演示了列表it的{​​{1}} lazily yields元素如何可迭代。

x

Kounis'启发的代码

如果急切需要评估,我们可以期望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 x[-1] = 0的影响为零,并期望打印it。显然不是这种情况,并且表明,按照相同的原理,我们的 4 循环在分配给for,每次迭代。

答案 1 :(得分:24)

(这不是一个答案,而是长篇幅的评论-已经有好几个了,尤其是@TrebledJ's。但是我不得不从覆盖已经具有值的变量的角度来明确地考虑它。为我点击了。)

如果有

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]

答案 2 :(得分:11)

在每次迭代中,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

答案 3 :(得分:10)

这是一个有趣的问题,您可以通过它理解它:

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]

希望对您有所帮助,如果还有其他问题,请发表评论。 :)

答案 4 :(得分:8)

answer by 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)样式,则这应该是一个有效的用例。

答案 5 :(得分:4)

a[-1]a的最后一个元素,在本例中为a[3]for循环有点不寻常,因为它使用此元素作为循环变量。 它不是在循环输入时评估该元素,而是在循环的每次迭代中为其分配值。

因此,首先将a[-1]设置为0,然后将其设置为1,然后将其设置为2。最后,在最后一次迭代中,for循环将检索a[3],该点此时为{{1} },因此列表的结尾为2

更典型的[0, 1, 2, 2]循环使用简单的局部变量名称作为循环变量,例如for。在这种情况下,for x ...将在每次迭代时设置为下一个值。除了每次迭代将x设置为下一个值外,这种情况没有什么不同。您不会经常看到这种情况,但这是一致的。