4. Python 的流程控制
Hi,大家好。我是茶桁。
在前面几节课的基础之上,我们今天开始尝试在 Python 中控制流程。这中间,让我们来做一些实际的练习。
Python 语句的分类
让我们先了解一下 Python 语句的分类。
在 Python 中,可分为单行代码
和代码块/组
, 顾名思义,单行代码就是一行的 Python 代码,而代码块是以冒号作为开始,用缩进划分相同作用域,这样的结构称之为代码块,是一个整体。
以上代码中输出结果为:
在输入代码块的时候,我们要注意使用缩进。在其他语言中代码块可能是{}
,但是在 Python 中严格遵守的缩进规则就是代码块。缩进可以是一个 Tab 距离或者四个空格,可是注意绝对不能混合使用,必须自使用一种方式缩进。
流程控制的分类
什么是流程?流程就是计算机执行代码时候的顺序。
流程可以被分为以下几类:
顺序结构
分支结构/选择结构
循环结构
顺序结构
顺序结构是系统的默认程序结构,自上而下进行执行。
分支结构
分支结构可以让代码走向不同的方向,不同的分支区间。
分支结构中又包含了单向分支,双分支和多分支以及巢状分支。
单向分支
单向分支就是在条件满足之后,执行后续任务。条件不满足的情况下,则不执行。
比如:
执行结果:
一个经典案例:
程序员下班前女朋友打电话:下班路上买十个包子回来,如果看到卖西瓜的买一个
输出结果:
正常情况下,我们是直接买了 10 个包子回家,那如果我们看到了卖西瓜的呢?那么这段代码中等于是我们重新赋值了mxg
, 就变成:
输出结果:
双分支
双分支就是在单向分支的基础之上,又多了一个“否则”的选项,当条件不满足的时候执行其他操作。
执行结果:
以上就是一个双向的流程控制,这里面的含义为:表达式成立则执行真区间,如果不成立则执行假区间。
多分支
多分支就是在双分支的基础之上再增加其他可能出现的判断条件,用于执行更多的其他操作。
这段代码中的elif
就是可能出现的不同条件,示例如下:
执行结果:
可以看到以上代码中,是从上到下依次进行判断条件,当所有条件都没有满足的时候,最后走到了else
区间。
这就是多分支,需要判断多个表达式的结果,会自行其中符合条件的一个。
巢状分支
巢状分支,也就是嵌套分支。也就是 if 条件语句的嵌套:
示例:
输出结果:
在嵌套分支中我们需要注意,3 ~ 5 层嵌套就是极限了,不要再往后嵌套。如果这个层数无法解决你的问题,那么可以重新梳理一下逻辑。基本大部分时候都是逻辑上有问题了。
分支 练习:十二生肖
当用户输入一个四位数的年份,计算当前这个年份对应的生肖:
申猴 酉鸡 戌狗 亥猪 子鼠 丑牛 寅虎 卯兔 辰龙 已蛇 午马 未羊
我们先来做一个用户输入的操作
添加一个type()
函数是为了验证用户输入之后的数据类型,当我们输入2023
之后,可以看到输出结果为:
证明虽然我们输入的是数字,但是被转成了字符串,那这个时候,我们就需要处理一下了:
输出结果:
这下就对了。
原本我们是需要讲位数,以及范围都控制在合理的数据内的。因为时间关系,在这整个示例中,我就不再去做更多的验证判断了。
当我输入2023
的时候,程序输出结果:
程序是正常运行了(排除我没做特殊处理可能会出现的 BUG),但是我们看这段代码,已经不能用丑陋来形容了。
让我们再改动一下,还记得咱们第二节课程中所学的list
吗?这段代码中我们去判断的num
是不是和list
的下标是一模一样?OK,让我们利用下标来重新写一下这段代码:
这段代码输出结果为:
是不是比起第一段代码来优雅多了?
循环结构
在完成了分支结构之后,我们来看一下循环结构。循环结构非常重要,必须熟练掌握。
为什么我们需要循环呢?先来看一段代码:
这段代码中,我们重复做了很多次打印的工作。这种事情,其实完全没必要重复去做,交给循环就可以了。
目前在 Python 中常用的循环有两个,while
循环和for...in
循环。
while 循环
在 while 循环中,我们通常都会写带有条件变化的循环
输出结果:
在这样一段代码中,在进入循环的时候,判断了一下当前条件是否成立。我们先设定了num
的值为 1,满足进入循环的条件,所以就进入了循环体,然后输出了num
的值。
之后,每循环一次我们都对num
做一次+1 的处理. 也就是更改了变量。当变量更改后,会重新走到循环体的开始去判断条件。在循环 11 次之后,num
就变成了 11,不符合进入循环的条件了,循环自然被终止。也可以说,更该变量也是在朝着循环结束的方向在前进。
那么如果我们没有设定这个num
的条件变化呢,自然就是无限的循环下去,最终导致内存溢出。
for 循环
通常来说,for
循环是用来遍历一个容器类型的数据。
使用for...in
循环遍历容器类型数据,那么中间的自定义变量就是当前容器类型中的每一个元素。
示例:
输出结果:
在整个for...in
循环体内,我们经常使用range()
函数来迭代输出一个范围,比如:
输出结果为:
可以看到我们输出了从0
开始,一直到9
结束, 一共输出了10
个数字。
从结果中,我们大致可以猜到range()
函数中(a, b)
的含义为:从 a 开始循环输出,输出到 b(不包含 b)为止, 比如,我们将刚才的数字改为range(1,8)
,那么我们最后输出的内容就会是:
其他流程控制语句
在循环体中,我们还经常应用一些其他的控制语句,用于程序的正常执行和中止。这其中包括
continue 语句, 用于跳过当前这一次循环
break 语句,用于结束或者跳出
pass 语句, 用于占位
输出结果:
可以从结果中看到,每次num
为偶数的时候,打印并未执行,被跳过了。
让我们来更改一下这一段代码:
输出结果为:
结果中我们可以看到,代码只输出到了 5。我们来剖析一下整个代码,当代码为 5 的时候,print()
函数还是正常执行了一次,然后再进来的时候,num
在最前方+1 变为了 6,执行了continue
,跳出了本次循环。再进入循环之后,num
+1 变成了 7,这个时候进入了第二个if
判断,直接执行了break
语句,跳出并结束了整个循环。这样,print()
函数这无法再继续执行下去了。
特殊语句
exit()
quit()
这两个特殊语句,均是用于结束程序的执行,exit()
和quit()
之后的代码不会执行。在单纯的循环结构中的作用与break
很像,但是完全不能混为一谈。这两个语句是用于结束并退出当前 python 解释器的,而break
仅用于结束当前的循环体。
练习
打印矩形
让我们循环出十行十列 ★ ☆ ,隔一行换色,再做一个隔一列换色。
在最开始,我们先思考一下,十行十列,那就是完成 100 次打印。我们先把这部分实现一下:
输出结果因为占篇幅,我就不写了,大家自行执行就可以了。
在这之后,我们需要考虑一下,既然是十行十列,那说明我们每隔 10 个就需要一次换行:
现在打印出了十行☆
, 每一行十个。第一步我们已经实现了,那么现在,让我们来尝试一下隔一行打印一个不同的。思考一下,其实就是奇偶数的问题,想明白之后,接下来就好办了:
隔列换色实现之后,我们再来考虑一下隔行换色,让我们从隔列换色上找一点灵感。既然隔列换色是奇偶数的问题,那么隔行换色,是不是就是每一行的奇偶数问题?
那么我们如何对行数做判断呢?其实很简单,我们只要对当前数字做取整数操作:num // 10
,然后获取到的整数再来取余就行了。
那么我们就可以这样来实现:
大家可以执行去操作一下试试,建议使用 Jupyter Notebook,这种实验性的代码块,很方便得到结果。
打印乘法口诀表
这也是 Python 教学中经常被拿来进行教学的一个经典案例,和上一个练习一样,我们一边分析,便来完善代码。
整个代码中,我们用到了刚才学到的for...in
循环以及range()
函数。
首先我们利用range()
函数,输出 1 到 9,每输出一个换一次行:
然后我们在每一行内再做一次循环,输出每一行的序列, 当然还是从 1 开始。
这里需要注意,就乘法表而言,我们最大列不能大于这一行的被乘数, 那么我们range()
需要调整一下:
斐波那契数列
再来让我们多做一个练习,斐波那契数列。
在做这个练习之前,首先我们需要了解什么是斐波那契数列。我这里应用一下维基百科的解释:
斐波那契数所形成的数列称为斐波那契数列。这个数列是由意大利数学家斐波那契在他的《算盘书》中提出。在数学上,斐波那契数是以递归的方法来定义:
用文字来说,就是斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:1、 1、 2、 3、 5、 8、 13、 21、 34、 55、 89、 144、 233、 377、 610、 987……
了解之后,让我们来分析一下:
第 0 项如果是 0,那么第一项是 1, 第二项也是 1, 之后的第三项开始,每一项都是前面两个数的和。
因为这个数列是一个无限递归下去的数列,我们不能无限的计算下去,所以需要先知道自己计算多少项:
之前我们分析得到,第三项开始,每一项是前面两个数的和,那么,我们需要定义两个变量用来承载相加的两个数,再多定义一个初始值,用于判断是否执行循环:
然后,让我们开始进入正题,需要先判断用户输入的数字是否正整数,我们先不搞那么复杂,只需要简单判断一下是否大于等于 0,然后再判断用户输入是否为 1, 因为如果是只输出 1 项,那么就不需要计算了,直接输出n1
就好了:
然后,让我们正式进入循环计算, 现在n1
为第一项,n2
就是第二项,直接输出就可以了
之后,我们去判断count
是否小于用户输入的数字,如果小于,就进入循环。然后再循环内定义一个变量n3
, 用来承载相加之后得到的结果,作为当前项输出。再讲n1, n2
重新赋值。不要忘了给count
加值。
当我们输入 9 的时候,输出结果:
百钱买百鸡
让我们先来说明一下这个题目:
在这个题目里,我们可以计算如果只买一种,这公鸡可以有 33 只,母鸡有 100 只,小鸡这可以买 200 只。
这里面我们可以思考一下,这里我们一共需要 3 个变量和 2 个常量,变量为公鸡,母鸡以及小鸡;2 个常量为 100 块钱和总共 100 只鸡。
先让我们从循环体来开始写:
这里,我们是计算了三只都买的情况,那么其实还有一种额外的情况,就是我们一开始说的,100 块钱都买母鸡的情况,也正好是 100 块钱 100 只鸡。所以,我们的num
要从 1 开始计数
输出结果(只看 num):
也就是说,我们目前一共有 20 种组合方案。具体有哪些方案,有兴趣的小伙伴可以执行我所写的代码,会打印出来。
虽然解决问题了,可是这并不是最好的写法。
看看这团垃圾的效率:第一层需要计算 34 次, 第一层每次计算,第二层都要计算 100 次,第二层每跑一遍,第三层需要计算 200 次.... 这简直就是一堆米田共。当我们加上一个计数变量稍微统计一下到底计算了多少次
可以得到最后结果为:
是不是很恐怖?让我们改动一下代码,优化性能:
让我们来思考一下,100 只鸡这个总数是不是固定不变的?那么公鸡,母鸡的计算得到之后,是不是小鸡的数量就得到了。还有必要在进入一次循环吗?肯定没必要了对不对?所以我们这样改动:
最后得到的计算结果:
从 660000 次一下下降到了 3300 次,这个性能的提示是很大的了。
所以,很多问题我们不要只追求解决,要善于多思考。
那么至此,我们这节课也就结束了。让我们最后放几个思考题:
思考题
对于我们打印矩阵,完成了隔行上色和隔列上色的问题,我们思考一下该如何解决
三角形和菱形
;对于乘法表,思考下我们如何完成反向打印。
解决了思考题的小伙伴,可以在评论区留言。期待看到大家的想法。我是茶桁,咱们下次见,下一节课,我们进入「模块化编程」,开始学习函数。
版权声明: 本文为 InfoQ 作者【Hivan】的原创文章。
原文链接:【http://xie.infoq.cn/article/9196b12948ffa6e93027e4d25】。文章转载请联系作者。
评论