python的for循环实质

由于个人技术能力有限,本文章有猜测成分,并不能证明for loop的实质确确实实就是这样。
由于我Google了一下没有人对此问题写过文章,发过提问或者解过答(可能有国外文章有解释,因为我只搜索了中文页面,只稍稍浏览了一下我能看懂的英文页面),故写此文
如需寻起根本,请查看CPython源代码
(为什么我不去看源码?因为我看不懂)

起因

在做题时有一道题,具体的已经忘记了,那里面有个让我很迷惑的for loop,让我以为是题目出错了,结果并不是。

1
2
3
4
5
6
7
s1 = 'v?a?s1?2df34g?h43j?4kl'
s = ''
for i in s1:
if i != '?':
s += i
s1 = s
# print(s1)

我觉得有问题的代码如上
按照我的理解,如果s1的第一个字符不是'?',那么s += i;s1 = s将会被执行,s将变成i,s1将变成s,按照上面给的数据,s1将变成单个字符v,然后循环结束,但事实不是这个样子的

经过

第一次尝试

1
2
3
4
a = [1, 2, 3, 4, 5, 6]
for i in a:
print(i)
a = [114514]

得到了

1
2
3
4
5
6
1
2
3
4
5
6

这与原题的现象是一致的,但当我不直接给a赋值,而是改变a本身时

1
2
3
4
a = [1, 2, 3, 4, 5, 6]
for i in a:
print(i)
a.pop() #等价del a[-1],当然结果也是一样的

得到了

1
2
3
1
2
3

每次执行print()之后都删除最后一个元素,所以只打印出了前三个元素,非常有道理是吧


第二次尝试

那为什么对a赋值就不行了呢
显然是python的底层原理问题,再看一个例子

1
2
3
4
5
6
7
8
9
a = [1, 2, 3, 4, 5, 6]
print(f'原始{id(a)=}')
for i in a:
print(i, '-', f'{id(i)=}')
print(f'(前):循环体中的{id(a)=}')
a = [1, 1, 4, 5, 1, 4]
print(f'(后):循环体中的{id(a)=}')
print(f'{id([1, 1, 4, 5, 1, 4])=}')
print('分割线'.center(100, '-'))

得到了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
原始id(a)=1704550319360
1 - id(i)=140728482469648
(前):循环体中的id(a)=1704550319360
(后):循环体中的id(a)=1704550399744
id([1, 1, 4, 5, 1, 4])=1704551518784
------------------------------------------------分割线-------------------------------------------------
2 - id(i)=140728482469680
(前):循环体中的id(a)=1704550399744
(后):循环体中的id(a)=1704551518784
id([1, 1, 4, 5, 1, 4])=1704550399744
------------------------------------------------分割线-------------------------------------------------
3 - id(i)=140728482469712
(前):循环体中的id(a)=1704551518784
(后):循环体中的id(a)=1704550399744
id([1, 1, 4, 5, 1, 4])=1704551518784
------------------------------------------------分割线-------------------------------------------------
4 - id(i)=140728482469744
(前):循环体中的id(a)=1704550399744
(后):循环体中的id(a)=1704551518784
id([1, 1, 4, 5, 1, 4])=1704550399744
------------------------------------------------分割线-------------------------------------------------
5 - id(i)=140728482469776
(前):循环体中的id(a)=1704551518784
(后):循环体中的id(a)=1704550399744
id([1, 1, 4, 5, 1, 4])=1704551518784
------------------------------------------------分割线-------------------------------------------------
6 - id(i)=140728482469808
(前):循环体中的id(a)=1704550399744
(后):循环体中的id(a)=1704551518784
id([1, 1, 4, 5, 1, 4])=1704550399744
------------------------------------------------分割线-------------------------------------------------

id()是什么?
f’{…=}’是什么?

可以清晰看到a指向的内存区域不断变化,确实和最原始的a不同了

(后):循环体中的id(a) 与 id([1, 1, 4, 5, 1, 4]) 不停交换是因为a频繁赋同样的值,a总得做出改变,但又为了节省空间,python创建了两个[1, 1, 4, 5, 1, 4]列表对象,每次a就换一个内存区域指

但是输出的元素还是最原始的a

结论

python for loop的实质是取地址
严格来说是python for loop 迭代可迭代对象时的实质
毕竟其他情况我不知道,没实验过
进入for i in a的循环体后,i的变化只在最原始的a映射范围内移动
无论之后a被重新指向了哪里,i的变化只在最原始的a映射范围内移动
为什么改变a本身时奇怪现象不存在呢,因为a并没有指向新地址,a的地址对应的映射关系确实发生了改变(即a的元素发生改变),i的变化只在最原始的a映射范围内移动