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

Kun*_*rma 92 python indexing for-loop

我有以下代码:

a = [0,1,2,3]

for a[-1] in a:
  print(a[-1])
Run Code Online (Sandbox Code Playgroud)

输出为:

0
1
2
2
Run Code Online (Sandbox Code Playgroud)

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

Tre*_*edJ 92

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

“咦?分配?有什么必须做的与我的输出是什么?”

确实,它与输出和结果有关。让我们进入一个for-in循环文档

for_stmt ::=  "for" target_list "in" expression_list ":" suite
Run Code Online (Sandbox Code Playgroud)

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

(强调)
NB的套件引用的语句(S)的换块下,print(a[-1])在我们的具体情况。

让我们玩得开心,扩展print语句:

a = [0, 1, 2, 3]
for a[-1] in a:
    print(a, a[-1])
Run Code Online (Sandbox Code Playgroud)

这给出以下输出:

[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)
Run Code Online (Sandbox Code Playgroud)

(添加了评论)

在这里,a[-1]每次迭代都有变化,我们看到此变化传播到a。同样,由于slicing是有效目标,因此这是可能的。

Ev提出了一个很好的论据Kounis使用上面引用的文档的第一句话:“ 表达式列表被评估一次 ”。这是否表示表达式列表是静态且不可变的,常量为[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
Run Code Online (Sandbox Code Playgroud)

代码受Kounis启发

如果急切需要评估,我们可以期望x[-1] = 0对它的影响为零,it并希望4将其打印出来。显然不是这种情况,而是要表明,根据相同的原理,我们的for-loop懒惰地产生从每次迭代a后的赋值到的数字a[-1]

  • 从本质上讲,这里要注意的重要一点是,Python认为a [-1]是赋值语句左侧的有效形式(例如,a [-1] = 1是有效的语法)。因此,“ a [-1]”是有效的“变量”名称,因为如文档所述,它像在赋值的左侧一样,在for循环声明中求值绑定变量。 (12认同)
  • 该答案缺少一个重点,即语法如何定义[`target_list`](https://docs.python.org/3/reference/simple_stmts.html#grammar-token-target-list)。由于我完全不清楚的原因,语法明确地(而不是“偶然地”)允许目标列表包含切片表达式(“ for”循环的语法只是回收普通的分配目标,这似乎很奇怪)。 (5认同)
  • @ Ev.Kounis不,它被评估一次以创建一个可迭代的对象。但是该可迭代对象仍然迭代原始数据,而不是其副本。 (3认同)
  • @KonradRudolph [我知道..](https://repl.it/repls/CurlySplendidAutosketch) (2认同)

Nat*_*han 24

(这比给出答案更像是一条漫长的评论-已经有好几个了,尤其是@ TrebledJ's。但是我不得不在覆盖变量之前就明确地考虑它,而对于我来说,这些变量已经有值了。)

如果你有

x = 0
l = [1, 2, 3]
for x in l:
    print(x)
Run Code Online (Sandbox Code Playgroud)

您不会感到惊讶,x每次循环都将其覆盖。即使x以前存在,也不会使用它的值(即for 0 in l:,这将引发错误)。而是将值从分配lx

当我们做

a = [0, 1, 2, 3]

for a[-1] in a:
  print(a[-1])
Run Code Online (Sandbox Code Playgroud)

即使a[-1]已经存在并且具有值,我们也不会放入该值,而是a[-1]通过循环将其分配给每个时间。


blh*_*ing 11

for在每次迭代中,循环语句的左侧表达式都与右侧的iterable中的每个项目相关联,因此

for n in a:
    print(n)
Run Code Online (Sandbox Code Playgroud)

只是一种幻想的方式:

for i in range(len(a)):
    n = a[i]
    print(n)
Run Code Online (Sandbox Code Playgroud)

同样

for a[-1] in a:
  print(a[-1])
Run Code Online (Sandbox Code Playgroud)

只是一种幻想的方式:

for i in range(len(a)):
    a[-1] = a[i]
    print(a[-1])
Run Code Online (Sandbox Code Playgroud)

其中,在每次迭代中,的最后一项a分配为中的下一项a,因此,当迭代最终到达最后一项时,其值最后分配为倒数第二项2


rec*_*nac 10

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

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

print(a)
Run Code Online (Sandbox Code Playgroud)

实际上a变成:[0, 1, 2, 2]after loop

输出:

0
1
2
2
[0, 1, 2, 2]
Run Code Online (Sandbox Code Playgroud)

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


ger*_*rit 8

TrebledJ答案解释了为什么可以做到这一点的技术原因。

您为什么要这样做?

假设我有一个对数组进行运算的算法:

x = np.arange(5)
Run Code Online (Sandbox Code Playgroud)

我想使用第一个索引的不同值来测试算法的结果。我可以简单地跳过第一个值,每次都重建一个数组:

for i in range(5):
    print(np.r_[i, x[1:]].sum())
Run Code Online (Sandbox Code Playgroud)

np.r_

这将在每次迭代时创建一个新数组,这是不理想的,特别是在数组很大的情况下。要在每次迭代中重复使用相同的内存,我可以将其重写为:

for i in range(5):
    x[0] = i
    print(x.sum())
Run Code Online (Sandbox Code Playgroud)

哪个也可能比第一个版本更清晰。

但这与更紧凑的编写方式完全相同:

for x[0] in range(5):
    print(x.sum())
Run Code Online (Sandbox Code Playgroud)

以上所有都会导致:

10
11
12
13
14
Run Code Online (Sandbox Code Playgroud)

现在,这是一个琐碎的“算法”,但是会有更复杂的用途,其中人们可能想要测试将数组中的单个(或多个,但由于赋值拆包而使事情复杂化)更改为多个值,最好不要复制整个数组。在这种情况下,您可能想在循环中使用索引值,但要做好准备,以防混淆任何维护您代码的人(包括您自己)。因此,x[0] = i最好使用显式分配的第二个版本,但是如果您更喜欢紧凑的for x[0] in range(5)样式,则这应该是一个有效的用例。


Tom*_*zes 5

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]在每次迭代时设置为下一个值。这种情况并不常见,但它是一致的。