我有以下代码:
a = [0,1,2,3]
for a[-1] in a:
print(a[-1])
输出为:
0
1
2
2
我对为什么列表索引可以用作for循环中的索引变量感到困惑。
答案 0 :(得分:91)
表达式a[-1]
中的列表索引,例如for a[-1] in a
如for_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
设置为下一个值外,这种情况没有什么不同。您不会经常看到这种情况,但这是一致的。