前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >理解for循环的运行机制

理解for循环的运行机制

作者头像
老齐
发布2021-03-11 15:11:40
发布2021-03-11 15:11:40
1.4K00
代码可运行
举报
文章被收录于专栏:老齐教室老齐教室
运行总次数:0
代码可运行

注: 本文是对《Python大学实用教程》和《跟老齐学Python:轻松入门》中关于for循环内容的提升。


在Python语言中,for循环非常强大,乃至于通常都不怎么提倡使用递归,所有遇到递归的时候,最好都改为for循环。对于初学者而言,for循环理解起来并不难,一般的入门读物中也都这么解释:

代码语言:javascript
代码运行次数:0
运行
复制
>>> lst = [0,1,2,3]
>>> for i in lst:
...     print(i)
...
0
1
2
3

变量 i依次引用列表list中的每个元素。比如我在自己的两本书《Python大学实用教程》和《跟老齐学Python:轻松入门》中,都是用这种方法对for循环进行了说明。

但是——转折了,非常重要——这种解释仅仅是就表象上向初学者做的解释,并没有揭示for循环的内在运行机制

我在《Python大学实用教程》一书中,曾以下面的方式对for循环做了深入阐述(参阅190页):

从这里我们知道,在进行 for循环的时候,其实是将被循环的对象转换为了可迭代对象——注意这个转换,非常重要。转换了之后,for循环是怎么运行的?在书中并没有深入讲解,下面我们就此给予介绍。

首先说明,本文内容的最主要参考或者说根据,就是Python语言的官方文档:https://docs.python.org/3/reference/compound_stmts.html#for,在这里对for循环语句有非常详细的说明。

代码语言:javascript
代码运行次数:0
运行
复制
for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

按照上述文档中的说明,对于前面的示例,将列表lst=[0,1,2,3]作为for循环语句中的expression_list,即将其转化为可迭代对象,并且只转化一次,不妨用iter_lst表示这个可迭代对象。然后就依次将这个可迭代对象的元素读入内存,并按照顺序,依次赋值给target_list。注意,不论target_list是什么,都是将所读入的可迭代对象匀速依次赋值。

用上面循环语句示例理解这段话,其分解动作如下:

  1. lst=[0,1,2,3]转换为可迭代对象,暂记作iter_lst
  2. 读入iter_lst的第一个元素0,并将它赋值给i(这里的i就对应着上面语法规则中的target_list
    1. 于是有:i=0
    2. pirnt(i),就打印出了0
  3. 读入iter_lst的第二个元素1,并将它赋值给i
    1. 于是有:i=1
    2. print(i),就打印出了1
\cdots

,按照上面的过程不断重复,直到最后一个元素`4`为止——因为`for`循环语句能够自动捕获迭代到最后一个元素之后的异常,所以,`for`循环能够在到达最后一个元素之后,结束循环。$$

代码语言:javascript
代码运行次数:0
运行
复制
>>> iter_lst=iter(lst)

用内置函数iter(),依据列表lst创建了一个可迭代对象iter_lst

代码语言:javascript
代码运行次数:0
运行
复制
>>> iter_lst=iter(lst)
>>> i = next(iter_lst)
>>> print(i)
0

这就完成了第一个循环。然后依次方式,向下循环:

代码语言:javascript
代码运行次数:0
运行
复制
# 第二个循环
>>> i = next(iter_lst)
>>> print(i)
1
# 第三个循环
>>> i = next(iter_lst)
>>> print(i)
2
# 第四个循环
>>> i = next(iter_lst)
>>> print(i)
3
# 到最后一个元素后面,抛出异常
>>> i = next(iter_lst)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

上面的演示,如果连贯起来,就是for循环——貌似没有什么奇怪的。

下面就要见证奇迹了。

经过上述操作之后,列表lst并没有发生变化——好像是废话。

代码语言:javascript
代码运行次数:0
运行
复制
>>> lst
[0, 1, 2, 3]

勿要着急,伟大总是孕育在平凡之中。

代码语言:javascript
代码运行次数:0
运行
复制
>>> iter_lst=iter(lst)
>>> i = next(iter_lst)
>>> print(i)
0
>>> lst
[0, 1, 2, 3]

完成第一循环,跟前面一样,并且在此时,查看了一下列表lst,没有变化。但是,我在这里做一个操作:

代码语言:javascript
代码运行次数:0
运行
复制
>>> lst[1]=111
>>> lst
[0, 111, 2, 3]

此时,将列表中序号为1的元素值修改为111,即lst[1]=111。如果按照读取可迭代对象的顺序,按照原来的流程,是要读取第二个元素1了,但是,在读取之间,我将列表中的第二个元素修改为111,那么,如果再进行下面的操作:

代码语言:javascript
代码运行次数:0
运行
复制
>>> i = next(iter_lst)

读取了可迭代对象的第二个元素,并把它赋值给变量i,此时,它是1还是111呢?

看结果:

代码语言:javascript
代码运行次数:0
运行
复制
>>> print(i)
111

不是1,而是111。再详细循环,就跟前述过程一样了。

这说明,如果将列表lst转换为可迭代对象之后,这个可迭代对象中的元素是对lst中元素的引用,并不是在可迭代对象中建立一套新的对象。

理解了上面的道理,看下面的操作,是不是能够解释?

代码语言:javascript
代码运行次数:0
运行
复制
>>> a = ['python', 'java', 'c', 'rust']
>>> iter_a = iter(a)
>>> a[1] = next(iter_a)
>>> a
['python', 'python', 'c', 'rust']

关键的一句是a[1] = next(iter_a)next(iter_a)得到了迭代器对象的第一个元素'python',并且将它赋值给a[1],这样,列表a中的索引是1的元素就变成了'python',即原来的'java'被替换为'python'了。

代码语言:javascript
代码运行次数:0
运行
复制
>>> a[1] = next(iter_a)
>>> a
['python', 'python', 'c', 'rust']

继续读取可迭代对象的第二个元素'python'

代码语言:javascript
代码运行次数:0
运行
复制
>>> a[1] = next(iter_a)
>>> a
['python', 'c', 'c', 'rust']

继续读取可迭代对象的第三个元素'c',在赋值给a[1],也就是列表a中的索引是1的元素变成了'c'

代码语言:javascript
代码运行次数:0
运行
复制
>>> a[1] = next(iter_a)
>>> a
['python', 'rust', 'c', 'rust']

这次a[1]='rust',根据前面的说明,应该容易理解了。

代码语言:javascript
代码运行次数:0
运行
复制
>>> a[1] = next(iter_a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

最后报异常了。如果将上述过程,写成for循环,是这样的:

代码语言:javascript
代码运行次数:0
运行
复制
>>> a = ['python', 'java', 'c', 'rust']
>>> for a[1] in a:
...     print(a, a[1])
...
(['python', 'python', 'c', 'rust'], 'python')
(['python', 'python', 'c', 'rust'], 'python')
(['python', 'c', 'c', 'rust'], 'c')
(['python', 'rust', 'c', 'rust'], 'rust')

上面循环语句中的a[1]就如同前面演示的i那样,都是循环语法结构中的target_list,只不过这里出了要完成赋值之外,还要同时实现对列表a中索引是1的元素修改,即实现上面分解动作中a[1] = next(iter_a)

似乎这里使用a[1]有点怪异。的确,在通常操作中很少这么做的。不过,上面的做法,倒是能让我们对for循环有了深刻理解。

理解了本文所介绍的内容,就不难回答stackoverflow上的一个问题了(https://stackoverflow.com/questions/55644201/why-can-i-use-a-list-index-as-an-indexing-variable-in-a-for-loop):

代码语言:javascript
代码运行次数:0
运行
复制
>>> b = [0,1,2,3]
>>> for b[-1] in b:
...     print(b[-1])
...
0
1
2
2

是否能自己解释这个结果?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-02-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 老齐教室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 注: 本文是对《Python大学实用教程》和《跟老齐学Python:轻松入门》中关于for循环内容的提升。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档