编程的力量——控制流

举报
G-washington 发表于 2020/02/15 04:13:41 2020/02/15
【摘要】 程序就是一系列指令。但编程真正的力量不仅在于运行(或“执行”)一条接一条的指令,就像周末的任务清单那样。根据表达式求值的结果,程序可以决定跳过指令,重复指令,或从几条指令中选择一条运行。实际上,你几乎永远不希望程序从第一行代码开始,简单地执行每行代码,直到最后一行。“控制流语句”可以决定在什么条件下执行哪些Python语句。

程序就是一系列指令。但编程真正的力量不仅在于运行(或“执行”)一条接一条的指令,就像周末的任务清单那样。根据表达式求值的结果,程序可以决定跳过指令,重复指令,或从几条指令中选择一条运行。实际上,你几乎永远不希望程序从第一行代码开始,简单地执行每行代码,直到最后一行。“控制流语句”可以决定在什么条件下执行哪些Python语句。

图1展示了一张流程图,内容是如果下雨怎么办。按照箭头构成的路径,从开始到结束。

在流程图中,通常有不止一种方法从开始走到结束。计算机程序中的代码行也是这样。流程图用菱形表示这些分支节点,其他步骤用矩形表示。开始和结束步骤用带圆角的矩形表示。

但在学习流程控制语句之前,首先要学习如何表示这些yes和no选项。同时你也需要理解,如何将这些分支节点写成Python代码。要做到这一点,让我们先看看布尔值、比较操作符和布尔操作符。

image.png

图1 一张流程图,告诉你如果下雨要做什么

1 布尔值

虽然整型、浮点型和字符串数据类型有无数种可能的值,但“布尔”数据类型只有两种值:True和False。Boolean(布尔)的首字母大写,因为这个数据类型是根据数学家George Boole命名的。在作为Python代码输入时,布尔值True和False不像字符串,两边没有引号,它们总是以大写字母T或F开头,后面的字母小写。在交互式环境中输入下面内容,其中有些指令是故意弄错的,它们将导致出错信息。

image.png

像其他值一样,布尔值也用在表达式中,并且可以保存在变量中❶。如果大小写不正确❷,或者试图使用True和False作为变量名❸,Python就会给出错误信息。

2 比较操作符

“比较操作符”比较两个值,求值为一个布尔值。表1列出了比较操作符。

表1 比较操作符

操作符

操作符含义
==等于
!=不等于
<小于
>大于
<=小于等于
>=大于等于

这些操作符根据给它们提供的值,求值为True或False。现在让我们尝试一些操作符,从==和!=开始。

image.png

如果两边的值一样,==(等于)求值为True。如果两边的值不同,!=(不等于)求值为True。==和!=操作符实际上可以用于所有数据类型的值。

image.png

请注意,整型或浮点型的值永远不会与字符串相等。表达式42 == '42'❶求值为False是因为,Python认为整数42与字符串'42'不同。

另一方面,<、>、<=和>=操作符仅用于整型和浮点型值。

image.png

操作符的区别


你可能已经注意到,==操作符(等于)有两个等号,而=操作符(赋值)只有一个等号。这两个操作符很容易混淆。只要记住:

  • ==操作符(等于)问两个值是否彼此相同。

  • =操作符(赋值)将右边的值放到左边的变量中。

为了记住谁是谁,请注意==操作符(等于)包含两个字符,就像!=操作符(不等于)包含两个字符一样。

你会经常用比较操作符比较一个变量和另外某个值。就像在例子eggCount <= 42❶和myAge >= 10❷中一样(毕竟,除了在代码中输入'dog' != 'cat'以外,你本来也可以直接输入True)。稍后,在学习控制流语句时,你会看到更多的例子。

3. 布尔操作符

3个布尔操作符(and、or和not)用于比较布尔值。像比较操作符一样,它们将这些表达式求值为一个布尔值。让我们仔细看看这些操作符,从and操作符开始。

(1) 二元布尔操作符

and和or操作符总是接受两个布尔值(或表达式),所以它们被认为是“二元”操作符。如果两个布尔值都为True,and操作符就将表达式求值为True,否则求值为False。在交互式环境中输入某个使用and的表达式,看看效果。

image.png

“真值表”显示了布尔操作符的所有可能结果。表2是操作符and的真值表。

表2 and操作符的真值表

表达式求值为
True and TrueTRUE
True and FalseFALSE
False and TrueFALSE
False and FalseFALSE

另一方面,只要有一个布尔值为真,or操作符就将表达式求值为True。如果都是False,所求值为False。

image.png

可以在or操作符的真值表中看到每一种可能的结果,如表3所示。

表3 or操作符的真值表

表达式求值为
True or TrueTRUE
True or FalseTRUE
False or TrueTRUE
False or FalseFALSE

(2) not操作符

和and和or不同,not操作符只作用于一个布尔值(或表达式)。not操作符求值为相反的布尔值。

image.png

就像在说话和写作中使用双重否定,你可以嵌套not操作符❶,虽然在真正的程序中并不经常这样做。表4展示了not的真值表。

表4 not操作符的真值表

表达式求值为
not TrueFALSE
not FalseTRUE

4 混合布尔和比较操作符

既然比较操作符求值为布尔值,就可以和布尔操作符一起,在表达式中使用。

回忆一下,and、or和not操作符称为布尔操作符是因为,它们总是操作于布尔值。虽然像4 < 5这样的表达式不是布尔值,但可以求值为布尔值。在交互式环境中,尝试输入一些使用比较操作符的布尔表达式。

image.png

计算机将先求值左边的表达式,然后再求值右边的表达式。知道两个布尔值后,它又将整个表达式再求值为一个布尔值。你可以认为计算机求值(4 < 5)和(5 < 6)的过程,如图2所示。

image.png

图2 (4 < 5)和 (5 < 6) 求值为True的过程

也可以在一个表达式中使用多个布尔操作符,与比较操作符一起使用。

image.png

和算术操作符一样,布尔操作符也有操作顺序。在所有算术和比较操作符求值后,Python先求值not操作符,然后是and操作符,然后是or操作符。

5 控制流的元素

控制流语句的开始部分通常是“条件”,接下来是一个代码块,称为“子句”。在开始学习具体的Python控制流语句之前,我将介绍条件和代码块。

(1) 条件

你前面看到的布尔表达式可以看成是条件,它和表达式是一回事。“条件”只是在控制流语句的上下文中更具体的名称。条件总是求值为一个布尔值,True或False。控制流语句根据条件是True还是False,来决定做什么。几乎所有的控制流语句都使用条件。

(2)代码块

一些代码行可以作为一组,放在“代码块”中。可以根据代码行的缩进,知道代码块的开始和结束。代码块有3条规则。

1.缩进增加时,代码块开始。

2.代码块可以包含其他代码块。

3.缩进减少为零,或减少为外面包围代码块的缩进,代码块就结束了。

看一些有缩进的代码,更容易理解代码块。所以让我们在一小段游戏程序中,寻找代码块,如下所示:

image.png

第一个代码块❶开始于代码行print('Hello Mary'),并且包含后面所有的行。在这个代码块中有另一个代码块❷,它只有一行代码:print('Access Granted.')。第三个代码块❸也只有一行:print('Wrong password.')。

6 程序执行

“程序执行”(或简称“执行”)这一术语是指当前被执行的指令。如果将源代码打印在纸上,在它执行时用手指指着每一行代码,你可以认为手指就是程序执行。

但是,并非所有的程序都是从上至下简单地执行。如果用手指追踪一个带有控制流语句的程序,可能会发现手指会根据条件跳过源代码,有可能跳过整个子句。

7 控制流语句

现在,让我们来看最重要的控制流部分:语句本身。语句代表了在图1的流程图中看到的菱形,它们是程序将做出的实际决定。

(1) if语句

最常见的控制流语句是if语句。if语句的子句(也就是紧跟if语句的语句块),将在语句的条件为True时执行。如果条件为False,子句将跳过。

在英文中,if语句念起来可能是:“如果条件为真,执行子句中的代码。”在Python中,if语句包含以下部分:

  • if关键字;

  • 条件(即求值为True或False的表达式);

  • 冒号;

  • 在下一行开始,缩进的代码块(称为if子句)。

例如,假定有一些代码,检查某人的名字是否为Alice(假设此前曾为name赋值)。

image.png

所有控制流语句都以冒号结尾,后面跟着一个新的代码块(子句)。语句的if子句是代码块,包含print('Hi, Alice.')。图3展示了这段代码的流程图。

image.png

图3 if语句的流程图

(2) else语句

if子句后面有时候也可以跟着else语句。只有if语句的条件为False时,else子句才会执行。在英语中,else语句读起来可能是:“如果条件为真,执行这段代码。否则,执行那段代码”。else语句不包含条件,在代码中,else语句中包含下面部分:

  • else关键字;

  • 冒号;

  • 在下一行开始,缩进的代码块(称为else子句)。

回到Alice的例子,我们来看看使用else语句的一些代码,在名字不是Alice时,提供不一样的问候。

image.png

图4 展示了这段代码的流程图。

image.png

图4 else语句的流程图

(3) elif语句

虽然只有if或else子句会被执行,但有时候可能你希望,“许多”可能的子句中有一个被执行。elif语句是“否则如果”,总是跟在if或另一条elif语句后面。它提供了另一个条件,仅在前面的条件为False时才检查该条件。在代码中,elif语句总是包含以下部分:

  • elif关键字;

  • 条件(即求值为True或False的表达式);

  • 冒号;

  • 在下一行开始,缩进的代码块(称为elif子句)。

让我们在名字检查程序中添加elif,看看这个语句的效果。

image.png

这一次,检查此人的年龄。如果比 12 岁小,就告诉他一些不同的东西。可以在图5中看到这段代码的流程图。

如果age < 12为True并且name == 'Alice'为False,elif子句就会执行。但是,如果两个条件都为False,那么两个子句都会跳过。“不能”保证至少有一个子句会被执行。如果有一系列的elif语句,仅有一条或零条子句会被执行。一旦一个语句的条件为True,剩下的elif子句会自动跳过。例如,打开一个新的文件编辑器窗口,输入以下代码,保存为vampire.py。

image.png

图5 elif语句的流程图

这里,我添加了另外两条elif语句,让名字检查程序根据age的不同答案而发出问候。图6展示了这段代码的流程图。

但是,elif语句的次序确实重要。让我们重新排序,引入一个缺陷。回忆一下,一旦找到一个True条件,剩余的子句就会自动跳过。所以如果交换vampire.py中的一些子句,就会遇到问题。像下面这样改变代码,将它保存为vampire2.py。

image.png

image.png

假设在这段代码执行之前,age变量的值是3000。你可能预计代码会打印出字符串'Unlike you, Alice is not an undead, immortal vampire.'。但是,因为age > 100条件为真(毕竟3000大于100)❶,字符串'You are not Alice, grannie.'被打印出来,剩下的语句自动跳过。别忘了,最多只有一个子句会执行,对于elif语句,次序是很重要的。

图7展示了前面代码的流程图。请注意,菱形age > 100和age > 2000交换了位置。

image.png

图7 vampire2.py程序的流程图。打叉的路径在逻辑上永远不会发生, 因为如果age大于2000,它就已经大于100了

你可以选择在最后的elif语句后面加上else语句。在这种情况下,保证至少一个子句(且只有一个)会执行。如果每个if和elif语句中的条件都为False,就执行else子句。例如,让我们使用if、elif和else子句重新编写Alicee程序。

image.png

图8展示了这段新代码的流程图,我们将它保存为littleKid.py。

image.png

图8 前面littleKid.py程序的流程图

在英语中,这类控制流结构会使得:“如果第一个条件为真,做这个。否则,如果第二个条件为真,做那个。否则,做另外的事。”如果你同时使用这3个语句,要记住这些次序规则,避免图2-7中那样的缺陷。首先,总是只有一个if语句。所有需要的elif语句都应该跟在if语句之后。其次,如果希望确保至少一条子句被执行,在最后加上else语句。

(4) while循环语句

利用while语句,可以让一个代码块一遍又一遍的执行。只要while语句的条件为True,while子句中的代码就会执行。在代码中,while语句总是包含下面几部分:

  • 关键字;

  • 条件(求值为True或False的表达式);

  • 冒号;

  • 从新行开始,缩进的代码块(称为while子句)。

可以看到,while语句看起来和if语句类似。不同之处是它们的行为。if子句结束时,程序继续执行if语句之后的语句。但在while子句结束时,程序执行跳回到while语句开始处。while子句常被称为“while循环”,或就是“循环”。

让我们来看一个if语句和一个while循环。它们使用同样的条件,并基于该条件做出同样的动作。下面是if语句的代码:

image.png

下面是while语句的代码:

image.png

这些语句类似,if和while都检查spam的值,如果它小于5,就打印一条消息。但如果运行这两段代码,它们各自的表现非常不同。对于if语句,输出就是"Hello, world."。但对于while语句,输出是"Hello, world."重复了5次!看一看这两段代码的流程图,图9和10,找一找原因。

image.png

图9 if语句代码的流程图

image.png

图10 while语句代码的流程图

带有if语句的代码检查条件,如果条件为True,就打印一次"Hello, world."。带有while循环的代码则不同,会打印5次。打印5次后停下来是因为,在每次循环迭代末尾,spam中的整数都增加1。这意味着循环将执行5次,然后spam < 5变为False。

在while循环中,条件总是在每次“迭代”开始时检查(也就是每次循环执行时)。如果条件为True,子句就会执行,然后,再次检查条件。当条件第一次为False时,while子句就跳过。

(5) 恼人的循环

这里有一个小例子,它不停地要求你输入“your name”(就是这个字符串,而不是你的名字)。选择FileNew Window,打开一个新的文件编辑器窗口,输入以下代码,将文件保存为yourName.py:

image.png

首先,程序将变量name❶设置为一个空字符串。这样,条件name != 'your name'就会求值为True,程序就会进入while循环的子句❷。

这个子句内的代码要求用户输入他们的名字,然后赋给name变量❸。因为这是语句块的最后一行,所以执行就回到while循环的开始,重新对条件求值。如果name中的值“不等于”字符串'your name',那么条件就为True,执行将再次进入while子句。

但如果用户输入your name,while循环的条件就变成'your name' != 'your name',它求值为False。条件现在是False,程序就不会再次进入while循环子句,而是跳过它,继续执行程序后面的部分❹。图11展示了yourName.py程序的流程图。

image.png

图11 yourName.py程序的流程图

现在,让我们来看看yourName.py程序的效果。按F5键运行它,输几次your name之外的东西,然后再提供程序想要的输入。

image.png

如果永不输入your name,那么循环的条件就永远为False,程序将永远问下去。这里,input()调用让用户输入正确的字符串,以便让程序继续。在其他程序,条件可能永远没有实际变化,这可能会出问题。让我们来看看如何跳出循环。

(6) break语句

有一个捷径,让执行提前跳出while循环子句。如果执行遇到break语句,就会马上退出while循环子句。在代码中,break语句仅包含break关键字。

非常简单,对吗?这里有一个程序,和前面的程序做一样的事情,但使用了break语句来跳出循环。输入以下代码,将文件保存为yourName2.py:

image.png

第一行❶创建了一个“无限循环”,它是一个条件总是为True的while循环。(表达式True总是求值为True。)程序执行将总是进入循环,只有遇到break语句执行时才会退出(“永远不”退出的无限循环是一个常见的编程缺陷)。

像以前一样,程序要求用户输入your name❷。但是现在,虽然执行仍然在while循环内,但有一个if语句会被执行❸,检查name是否等于your name。如果条件为True,break语句就会运行❹,执行就会跳出循环,转到print('Thank you!') ❺。否则,包含break语句的if语句子句就会跳过,让执行到达while循环的末尾。此时,程序执行跳回到while语句的开始❶,重新检查条件。因为条件是True,所以执行进入循环,再次要求用户输入your name。这个程序的流程图参见图12。

运行yourName2.py,输入你为yourName.py程序输入的同样文本。重写的程序应该和原来的程序反应相同。

image.png

图12 带有无限循环的程序的流程图。注意打叉路径在逻辑上 永远不会发生,因为循环条件总是为True

(7) continue语句

像break语句一样,continue语句用于循环内部。如果程序执行遇到continue语句,就会马上跳回到循环开始处,重新对循环条件求值(这也是执行到达循环末尾时发生的事情)。

让我们用continue写一个程序,要求输入名字和口令。在一个新的文件编辑窗口中输入以下代码,将程序保存为swordfish.py。

image.png

如果用户输入的名字不是Joe❶,continue语句❷将导致程序执行跳回到循环开始处。再次对条件求值时,执行总是进入循环,因为条件就是True。如果执行通过了if语句,用户就被要求输入口令❸。如果输入的口令是swordfish,break语句运行❹,执行跳出while循环,打印Access granted❺。否则,执行继续到while循环的末尾,又跳回到循环的开始。这个程序的流程图参见图13。

陷在无限循环中?


如果你运行一个有缺陷的程序,导致陷在一个无限循环中,那么请按Ctrl-C。这将向程序发送KeyboardInterrupt错误,导致它立即停止。试一下,在文件编辑器中创建一个简单的无限循环,将它保存为infiniteloop.py。

while True:   print('Hello world!')

如果运行这个程序,它将永远在屏幕上打印Hello world!因为while语句的条件总是True。在IDLE的交互式环境窗口中,只有两种办法停止这个程序:按下Ctrl-C或从菜单中选择ShellRestart Shell。如果你希望马上停止程序,即使它不是陷在一个无限循环中,Ctrl-C也是很方便的。

运行这个程序,提供一些输入。只有你声称是Joe,它才会要求输入口令。一旦输入了正确的口令,它就会退出。

image.png

image.png

图13 swordfish.py的流程图。打叉的路径在逻辑上永远不会执行,因为循环条件总是True

(8) for循环和range()函数

在条件为True时,while循环就会继续循环(这是它的名称的由来)。但如果你想让一个代码块执行固定次数,该怎么办?可以通过for循环语句和range()函数来实现。

“类真”和“类假”的值

image.png

在代码中,for语句看起来像for i in range(5):这样,总是包含以下部分:

  • for关键字;

  • 一个变量名;

  • in关键字;

  • 调用range()方法,最多传入3个参数;

  • 冒号;

  • 从下一行开始,缩退的代码块(称为for子句)。

让我们创建一个新的程序,名为fiveTimes.py,看看for循环的效果。

image.png

for循环子句中的代码运行了5次。第一次运行时,变量i被设为0。子句中的print()调用将打印出Jimmy Five Times (0)。Python完成for循环子句内所有代码的一次迭代之后,执行将回到循环的顶部,for语句让i增加1。这就是为什么range(5)导致子句的5次迭代,i分别被设置为0、1、2、3、4。变量i将递增到(但不包括)传递给range()函数的整数。图2-14展示了fiveTimes.py程序的流程图。

image.png

图14 fiveTimes.py的流程图

运行这个程序时,它将打印5次Jimmy Five Times和i的值,然后离开for循环。

image.png

也可以在循环中使用continue语句。continue语句将让for循环变量继续下一个值,就像程序执行已经到达循环的末尾并返回开始一样。实际上,只能在while和for循环内部使用continue和break语句。如果试图在别处使用这些语句,Python将报错。

作为for循环的另一个例子,请考虑数学家高斯的故事。当高斯还是一个小孩时,老师想给全班同学布置很多计算作业。老师让他们从0加到100。高斯想到了一个聪明办法,在几秒钟内算出了答案,但你可以用for循环写一个Python程序,替你完成计算。

image.png

结果应该是5050。程序刚开始时,total变量被设为0。然后for循环执行100次total = total + num。当循环完成100次迭代时,0到100的每个整数都加给了total。这时,total被打印到屏幕上。即使在最慢的计算机上,这个程序也不用1秒钟就能完成计算。

(小高斯想到,有50对数加起来是100:1 + 99, 2 + 98, 3 + 97……直到49 + 51。因为50 × 100 是5000,再加上中间的50,所以0到100的所有数之和是5050。聪明的孩子!)

(9) 等价的while循环

实际上可以用while循环来做和for循环同样的事,for循环只是更简洁。让我们用与for循环等价的while循环,重写fiveTimes.py。

image.png

运行这个程序,输出应该和使用for循环的fiveTimes.py程序一样。

(10) range()的开始、停止和步长参数

某些函数可以用多个参数调用,参数之间用逗号分开,range()就是其中之一。这让你能够改变传递给range()的整数,实现各种整数序列,包括从0以外的值开始。

image.png

第一个参数是for循环变量开始的值,第二个参数是上限,但不包含它,也就是循环停止的数字。

image.png

range()函数也可以有第三个参数。前两个参数分别是起始值和终止值,第三个参数是“步长”。步长是每次迭代后循环变量增加的值。

image.png

所以调用range(0, 10, 2)将从0数到8,间隔为2。

image.png

在为for循环生成序列数据方面,range()函数很灵活。举例来说,甚至可以用负数作为步长参数,让循环计数逐渐减少,而不是增加。

image.png

运行一个for循环,用range(5, -1, -1)来打印i,结果将从5降至0。

image.png

8 导入模块

Python程序可以调用一组基本的函数,这称为“内建函数”,包括你见到过的print()、input()和len()函数。Python也包括一组模块,称为“标准库”。每个模块都是一个Python程序,包含一组相关的函数,可以嵌入你的程序之中。例如,math模块有数学运算相关的函数,random模块有随机数相关的函数,等等。

在开始使用一个模块中的函数之前,必须用import语句导入该模块。在代码中,import语句包含以下部分:

  • import关键字;

  • 模块的名称;

  • 可选的更多模块名称,之间用逗号隔开。

在导入一个模块后,就可以使用该模块中所有很酷的函数。让我们试一试random模块,它让我们能使用random.ranint()函数。

在文件编辑器中输入以下代码,保存为printRandom.py:

image.png

random.randint()函数调用求值为传递给它的两个整数之间的一个随机整数。因为randint()属于random模块,必须在函数名称之前先加上random.,告诉python在random模块中寻找这个函数。

下面是import语句的例子,它导入了4个不同的模块:

image.png

现在我们可以使用这4个模块中的所有函数。本书后面我们将学习更多的相关内容。

from import语句

import语句的另一种形式包括from关键字,之后是模块名称,import关键字和一个星号,例如from random import *。

使用这种形式的import语句,调用random模块中的函数时不需要random.前缀。但是,使用完整的名称会让代码更可读,所以最好是使用普通形式的import语句。

9 用sys.exit()提前结束程序

要介绍的最后一个控制流概念,是如何终止程序。当程序执行到指令的底部时,总是会终止。但是,通过调用sys.exit()函数,可以让程序终止或退出。因为这个函数在sys模块中,所以必须先导入sys,才能使用它。

打开一个新的文件编辑器窗口,输入以下代码。保存为exitExample.py:

image.png

在IDLE中运行这个程序。该程序有一个无限循环,里面没有break语句。结束该程序的唯一方式,就是用户输入exit,导致sys.exit()被调用。如果response等于exit,程序就会中止。因为response变量由input()函数赋值,所以用户必须输入exit,才能停止该程序。


本文节选自《Python编程快速上手——让繁琐工作自动化》


内容简介


如今,人们面临的大多数任务都可以通过编写计算机软件来完成。Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。通过Python编程,我们能够解决现实生活中的很多任务。

本书是一本面向实践的Python编程实用指南。本书的目的,不仅是介绍Python语言的基础知识,而且还通过项目实践教会读者如何应用这些知识和技能。本书的第一部分介绍了基本的Python编程概念,第二部分介绍了一些不同的任务,通过编写Python程序,可以让计算机自动完成它们。第二部分的每一章都有一些项目程序,供读者学习。每章的末尾还提供了一些习题和深入的实践项目,帮助读者巩固所学的知识。附录部分提供了所有习题的解答。

本书适合任何想要通过Python学习编程的读者,尤其适合缺乏编程基础的初学者。通过阅读本书,读者将能利用最强大的编程语言和工具,并且将体会到Python编程的快乐。

本文章转载自异步社区

原文链接:https://www.epubit.com/articleDetails?id=NC7E3EF9167E00001D63B76681CA018D9

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。