购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

第2章
if-else和流程控制

目前,你对指令有了基本认识,还知道程序不过是一系列指令而已,但编程的真正强大之处并非像完成周末任务清单那样逐条执行指令。程序能够根据表达式的结果决定跳过指令、反复执行指令或从多条指令中选择一条并加以执行;实际上,你几乎不会希望程序从第1行代码开始逐条执行每行代码,直到到达程序末尾。流程控制语句能够根据当前条件决定要执行的Python指令。

这些流程控制语句直接对应流程图中的符号,因此对于本章讨论的代码,将提供与之对应的流程图。图2-1就是一个流程图,指出了下雨时该如何办:从“开始”到“结束”的一系列箭头指出了处理流程。

图2-1 指出下雨时该如何办的流程图

在流程图中,通常有多条从“开始”到“结束”的通道,计算机程序中的代码亦如此。在流程图中,分支点用菱形表示,其他步骤用矩形表示,而开始和结束步骤用圆角矩形表示。

要学习流程控制语句,必须先学习如何表示是和否选项,还需知道如何编写表示分支点的Python代码。为此,我们来探讨布尔值、比较运算符和布尔运算符。

2.1 布尔值

整数、浮点数和字符串等数据类型包含无穷多个值,但布尔数据类型只有两个值:True和False。在Python代码中,表示布尔值True和False时,无须像字符串那样将其放在引号内。另外,它们的首字母都大写(以T或F开头),而其他字母小写。因为这些布尔值没有放在引号内,所以它们不同于字符串'True'和'False'。请在交互式环境中输入下面的代码:

❶ >>> spam = True
  >>> spam
  True
❷ >>> true
  Traceback (most recent call last):
    File "<python-input-0>", line 1, in <module>
  NameError: name 'true' is not defined
❸ >>> False = 2 + 2
    File "<python-input-0>", line 1, in <module>
  SyntaxError: can't assign to False

这里故意编写了一些错误的指令,它们会导致Python显示错误消息。与其他所有值一样,布尔值也可用在表达式中,还可存储在变量中❶。如果大小写不正确❷或将True和False用作变量名❸,Python将显示错误消息。

2.2 比较运算符

比较运算符也称为关系运算符,它们对两个值进行比较,结果为布尔值。表2-1列出了各种比较运算符。

表2-1 比较运算符

这些运算符的结果是True还是False取决于提供给它们的值。下面来尝试使用一些比较运算符——从==和!=开始:

>>> 42 == 42
True
>>> 42 == 99
False
>>> 2 != 3
True
>>> 2 != 2
False

与你预期的一样,如果两边的值相同,比较运算符==(等于)的结果为True;如果两边的值不同,比较运算符!=(不等于)的结果为True 。实际上,运算符==和!= 可用于对任何数据类型的值进行比较:

  >>> 'hello' == 'hello'
  True
  >>> 'hello' == 'Hello'
  False
  >>> 'dog' != 'cat'
  True
  >>> True == True
  True
  >>> True != False
  True
  >>> 42 == 42.0
  True
❶ >>> 42 == '42'
  False

注意,整数值或浮点数值永远不可能与字符串值相等。表达式42 == '42' ❶的结果为False,因为在Python看来,整数42不同于字符串'42'。然而,Python确实认为整数42和浮点数42.0相等。

此外,运算符<、>、<=和>=只能用于整数值和浮点数值:

  >>> 42 < 100
  True
  >>> 42 > 100
  False
  >>> 42 < 42
  False
  >>> eggs = 42
❶ >>> eggs <= 42
  True
  >>> my_age = 29
❷ >>> my_age >= 10
  True

比较运算符常用于将变量的值与其他值进行比较(如在示例eggs <= 42 ❶和my_age >= 10 ❷中),或者对两个变量中的值进行比较(毕竟像'dog' != 'cat'这样比较两个字面量时,在任何情况下其结果都一样)。本章后面介绍流程控制语句时,你将看到更多有关比较运算符的示例。

运算符==和=的区别

你可能注意到了,运算符==(等于)由两个等号组成,而运算符=(赋值)只有一个等号。这两个运算符很容易混淆。请记住如下两点。

● 运算符==用于判断两个值是否相同。

● 运算符=将位于它右边的值赋给位于它左边的变量。

为帮助区分这两个运算符,请注意,运算符==(等于)由两个字符组成,就像运算符!=(不等于)也由两个字符组成一样。

2.3 布尔运算符

有3个布尔运算符(and、or和not)用于比较布尔值。与比较运算符一样,它们的结果也是布尔值。下面来详细研究这些运算符——从and运算符开始。

and运算符总是作用于两个布尔值(或表达式),因此被视为二元布尔运算符。如果两个布尔值都是True,and运算符的结果为True,否则为False。在交互式环境中输入一些包含and的表达式,看看这个运算符的工作原理:

>>> True and True
True
>>> True and False
False

真值表列出布尔运算符的所有可能结果。表2-2为and运算符的真值表。

表2-2 and运算符的真值表

与and运算符一样,or运算符也作用于两个布尔值(或表达式),因此它也被视为二元布尔运算符。然而,只要有一个布尔值为True,or运算符的结果就为True;换句话说,仅当两个布尔值都为False时,or运算符的结果才为False。

>>> False or True
True
>>> False or False
False

表2-3为or运算符的真值表,其中列出了该运算符的所有可能结果。

表2-3 or运算符的真值表

不同于运算符and和or,运算符not只作用于一个布尔值(或表达式),因此称它为一元运算符。not运算符的结果为与原布尔值相反的布尔值:

  >>> not True
  False
❶ >>> not not not not True
  True

可以嵌套使用not运算符❶,这很像在谈话或写作中的双重否定,但在实际程序中几乎不这样做。表2-4为not运算符的真值表。

表2-4 not运算符的真值表

2.4 混合使用布尔运算符和比较运算符

因为比较运算符的结果为布尔值,所以可在表达式中将它们作为布尔运算符的操作数。

前面说过,运算符and、or和not被称为布尔运算符,因为它们总是作用于布尔值True和False。在类似于4 < 5这样的表达式中,包含的值不是布尔值,但整个表达式的结果为布尔值。尝试在交互式环境中输入一些使用比较运算符的布尔表达式:

>>> (4 < 5) and (5 < 6)
True
>>> (4 < 5) and (9 < 6)
False
>>> (1 == 2) or (2 == 2)
True

计算机先计算左边的表达式,再计算右边的表达式。当它知道这两个表达式的布尔值后,就会计算整个表达式,得到一个布尔值。计算机计算表达式(4 < 5) and (5 < 6)的过程如下:

另外,在同一个表达式中,可以使用多个布尔运算符,还有比较运算符:

>>> spam = 4
>>> 2 + 2 == spam and not 2 + 2 == (spam + 1) and 2 * 2 == 2 + 2
True

与数学运算符一样,布尔运算符也有优先级:计算数学运算符和比较运算符后,Python先计算not运算符,再计算and运算符,最后计算or运算符。

2.5 流程控制的组成部分

流程控制语句通常以称作条件的部分开头,后面总是跟着称作子句的代码块。在介绍具体的Python流程控制语句前,先来说说条件和代码块。

2.5.1 条件

前面介绍过的布尔表达式都可视为条件,条件也是表达式,但指的是流程控制语句中的表达式。条件的结果总是布尔值,要么为True,要么为False。流程控制语句根据条件是True还是False决定接下来做什么,因此几乎所有的流程控制语句都使用条件。你经常需要编写下面这样的代码:如果这个条件为True,就做这件事,否则做另外一件事。你还经常需要编写这样的代码:只要这个条件为True,就不断地重复执行这些指令。

2.5.2 代码块

可以将Python代码行组合在一起,形成代码块。通过代码行的缩进情况,可以确定代码块的起始位置和结束位置。关于代码块,有4条规则。

● 缩进增大意味着接下来为一个新的代码块。

● 代码块中可以包含其他代码块。

● 缩进减少为零或减少至外面的代码块的缩进程度时,意味着当前代码块到此结束。

● Python要求以冒号结尾的语句后面紧跟着一个新的代码块。

通过查看一些缩进的代码,可以更轻松地理解代码块。鉴于此,我们来查找一个小型程序中的代码块,如下所示:

username = 'Mary'
password = 'swordfish' 
if username == 'Mary':
  ❶ print('Hello, Mary')
    if password == 'swordfish':
      ❷ print('Access granted.')
    else:
      ❸ print('Wrong password.')

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

2.5.3 程序执行

在第1章的程序hello.py中,Python从开头开始,按从前往后的顺序依次执行指令。程序执行(简称为执行)指的是当前执行的指令。如果将手指指向屏幕上当前执行的代码行,就可以跟踪程序执行的过程。

然而,并非所有程序都是按顺序从前往后执行的,如果用手指来跟踪包含流程控制语句的程序的执行过程,很可能发现手指将随条件变化不断地在源代码中跳来跳去。

2.6 流程控制语句

现在介绍流程控制的最重要部分:流程控制语句本身。流程控制语句对应图2-1所示流程图中的菱形,是程序实际做出的决定。

2.6.1 if语句

最常见的流程控制语句是if语句。如果条件为True,将执行if语句的子句,即紧跟在if语句后面的代码块;如果条件为False,将跳过该子句。

if语句的含义如下:如果这个条件为True,就执行子句中的代码。在Python中,if语句由以下部分组成。

● 关键字if。

● 条件,即结果为True或False的表达式。

● 冒号。

● 缩进的代码块(称为if子句或if代码块),始于接下来的一行。

例如,假设要编写一些代码来检查某人的名字是否为Alice,如下所示:

name = 'Alice'
if name == 'Alice': 
    print('Hi, Alice.')

所有流程控制语句都以冒号结尾,且紧跟着一个新的代码块(子句)。上面if语句的子句是一个代码块,包含代码print('Hi, Alice.')。图2-2展示了这些代码对应的流程图。

图2-2  一条if语句对应的流程图

如果将变量name的值改为其他字符串,再次运行这个程序,将发现屏幕上没有出现“Hi, Alice.”,因为程序执行时跳过了打印这些内容的代码。

2.6.2 else语句

在if子句后面,可能跟着else语句。仅当if语句的条件为False时,才会执行else子句,换句话说,else语句的含义如下:如果条件为False,就执行这些代码。else语句不包含条件,在代码中,else语句总是包含如下部分。

● 关键字else。

● 冒号。

● 缩进的代码块(称为else子句或else代码块),始于接下来的一行。

回到前面有关名字的示例,看看如何使用else语句在名字不是Alice时发出不同的问候:

name = 'Alice'
if name == 'Alice':
    print('Hi, Alice.')
else:
    print('Hello, stranger.')

图2-3展示了这些代码对应的流程图。

图2-3 一条else语句对应的流程图

如果将变量name的值改为其他字符串,再次运行这个程序,将发现屏幕上显示的是'Hello, stranger.',而不是'Hi, Alice.'。

2.6.3 elif语句

要执行两条子句之一时,可以使用if和else语句。但在有些情况下,需要执行众多子句 之一,可以使用if和elif语句。elif语句是一条“else if”语句,总是跟在if语句或另一条elif语句后面,它包含当前面的所有条件都为False时才会被检查的另一个条件。在代码中,elif语句总是包含如下部分。

● 关键字elif。

● 条件,即结果为True或False的表达式。

● 冒号。

● 缩进的代码块(被称为elif子句或elif代码块),始于接下来的一行。

下面在代码中添加一条elif语句,看看这条语句是如何工作的:

name = 'Alice'
age = 33
if name == 'Alice': 
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')

这里的elif语句检查年龄,如果小于12,就显示相应的信息。图2-4展示了这些代码对应的流程图。

图2-4 一条elif语句对应的流程图

如果age < 12为True且name == 'Alice'为False,就执行elif子句。然而,如果这两个条件都为False,Python将跳过所有的子句。不能保证至少有一条子句会被执行,在有一系列elif语句的情况下,将执行其中的一条子句或不执行任何子句。找到一条条件为True的语句后,将自动跳过其他所有的elif子句。请打开一个新的文件编辑器窗口,输入下面的代码,并将其保存为vampire.py:

name = 'Carol' 
age = 3000
if name == 'Alice': 
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')
elif age > 2000:
    print('Unlike you, Alice is not an undead, immortal vampire.')
elif age > 100:
    print('You are not Alice, grannie.')

这里新增了两条elif语句,让程序根据age的值以不同的方式问候用户。图2-5展示了这些代码的流程图。

图2-5 与程序vampire.py中多条elif语句对应的流程图

不同elif语句的排列顺序很重要,我们来重新排列它们,以引入bug。前面说过,找到一个结果为True的条件后,将跳过余下的所有elif子句,因此如果交换程序vampire.py中一些子句的位置,将出现问题。请将代码修改成下面这样,再保存为vampire2.py:

  name = 'Carol'
  age = 3000
  if name == 'Alice':
    print('Hi, Alice.')
  elif age < 12:
    print('You are not Alice, kiddo.')
❶ elif age > 100:
    print('You are not Alice, grannie.')
  elif age > 2000:
    print('Unlike you, Alice is not an undead, immortal vampire.')

假设这些代码执行前,变量age包含的值为3000,你可能以为这些代码将显示字符串'Unlike you, Alice is not an undead, immortal vampire.'。然而,因为条件age > 100为True(毕竟3000大于100)❶,所以将显示字符串'You are not Alice, grannie.',并自动跳过其余的elif语句。前面说过,最多会执行一个子句,因此elif语句的排列顺序很重要。

图2-6展示了这些代码对应的流程图。注意,相比于图2-5,这里交换了包含age > 100和age > 2000的菱形的位置。

图2-6 程序vampire2.py的流程图(从逻辑上说,路径X永远不会出现,因为如果age大于2000,它就必然大于100)

在最后一条elif语句后面,可添加一条else语句。如果这样做,将有(且只有)一个子句被执行。如果if语句和所有elif语句中的条件都为False,将执行else子句。例如,我们重新创建这个程序,在其中同时使用if、elif和else语句:

name = 'Carol'
age = 3000
if name == 'Alice':
    print('Hi, Alice.')
elif age < 12:
    print('You are not Alice, kiddo.')
else:
    print('You are neither Alice nor a little kid.')

图2-7展示了这些新代码(被保存为文件littleKid.py)对应的流程图。

图2-7 程序littleKid.py对应的流程图

这种流程控制结构的含义如下:如果第一个条件为True,就这样做;如果第二个条件为True,就那样做;否则就做第三件事情。同时使用if、elif和else语句时,务必牢记下面这些有关排列顺序的规则,以免出现类似于图2-6所示的bug。首先,有且只有一条if语句,所有elif语句都必须跟在if语句后面;其次,要确保有且只有一条子句被执行,就在末尾添加一条else语句。

如你所见,流程控制语句可让程序更精巧,但也可能导致程序更复杂。不用绝望,经过更多的编码练习后,你就能够更加得心应手地应对这种复杂性。所有程序员在其职业生涯中都会遇到如下情况:不小心将<=录入成了<,导致程序不能正确运行。这样的小错人人都会犯。

2.7 小程序1:反转日

学习布尔值和if-else语句后,来编写一些程序。打开一个新的文件编辑器窗口,输入下面的代码,并将其保存为oppositeday.py:

  today_is_opposite_day = True
 
  # 根据today_is_opposite_day的值相应地设置say_it_is_opposite_day
❶ if today_is_opposite_day == True: 
      say_it_is_opposite_day = True
  else:
      say_it_is_opposite_day = False
 
  # 如果今天是反转日,就反转say_it_is_opposite_day的值
  if today_is_opposite_day == True:
    ❷ say_it_is_opposite_day = not say_it_is_opposite_day
 
  # 指出今天是不是反转日
  if say_it_is_opposite_day == True:
      print('Today is Opposite Day.')
  else:
      print('Today is not Opposite Day.')

运行这个程序时,输出'Today is not Opposite Day.'。在这个程序中,有两个变量:在程序开头,将变量today_is_opposite_day设置成了True;接下来的if语句检查这个变量❶,如果它为True,就将变量say_it_is_opposite_day也设置为True,否则将变量say_it_is_opposite_day设置为False。第二条if语句检查today_is_opposite_day是否为True,如果是,就反转变量say_it_is_opposite_day(即将其设置为相反的布尔值)❷。最后,第三条if语句检查say_it_is_opposite_day是否为True,如果是,就显示'Today is Opposite Day.';否则就显示'Today is not Opposite Day.'。

如果将这个程序的第一行改为today_is_opposite_day = False,再次运行这个程序,它依然显示'Today is not Opposite Day.'。如果此时审视这个程序,将发现第一条if-else语句将say_it_is_opposite_day设置为False;第二条if语句的条件为False,因此其代码块被跳过;最后,第三条if语句的条件还是False,因此显示'Today is not Opposite Day.'。

因此,如果今天不是反转日,程序将正确地显示'Today is not Opposite Day.';如果今天是反转日,程序将显示'Today is not Opposite Day.',这也是正确的,因为反转日就该反着说。从逻辑上说,不管变量today_is_opposite_day被设置为True还是False,这个程序都不可能显示'Today is Opposite Day.'。实际上,这个程序与只包含代码print('Today is not Opposite Day.')的程序等效,这就是不应按程序员编写的代码行数支付报酬的原因所在。

2.8 小程序2:不诚实的容量计算器

第1章说过,硬盘和闪存制造商如何使用不同的TB和GB定义,在产品容量方面做虚假宣传。我们来编写一个程序,计算宣传的容量与实际容量相差多大。打开一个新的文件编辑器窗口,输入如下代码,并保存为dishonestcapacity.py:

print('Enter TB or GB for the advertised unit:')
unit = input('>')
 
# 计算宣传的容量与实际容量的比值
if unit == 'TB' or unit == 'tb':
    discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
    discrepancy = 1000000000 / 1073741824
 
print('Enter the advertised capacity:')
advertised_capacity = input('>')
advertised_capacity = float(advertised_capacity)
 
# 计算实际容量,并通过舍入保留两位小数
#再将其转换为字符串,以便进行拼接
real_capacity = str(round(advertised_capacity * discrepancy, 2))
 
print('The actual capacity is ' + real_capacity + ' ' + unit)

这个程序首先让用户输入宣传中使用的容量单位(TB或GB):

# 计算宣传的容量与实际容量的比值
if unit == 'TB' or unit == 'tb':
    discrepancy = 1000000000000 / 1099511627776
elif unit == 'GB' or unit == 'gb':
    discrepancy = 1000000000 / 1073741824

TB比GB大,而单位越大,宣传的容量与实际容量相差越多。这里的if和elif语句使用了布尔运算符or,确保不管用户输入的单位是大写还是小写,这个程序都能正常运行。如果用户输入的是其他单位,那么if子句和elif子句都不会运行,因此不会给变量discrepancy赋值。后面在程序试图使用变量discrepancy时,就会引发错误。这一点将稍后介绍。

接下来,用户输入宣传的容量(以指定的单位为单位):

# 计算实际容量,并通过舍入保留两位小数
#再将其转换为字符串,以便进行拼接
real_capacity = str(round(advertised_capacity * discrepancy, 2))

这行代码做了很多工作,以用户输入的单位TB和宣传容量10为例来说明这行代码的执行情况。在这行代码的最里面,将advertised_capacity和discrepancy相乘。结果为实际容量,但可能有很多位小数。鉴于此,将这个结果作为第一个参数传递给round(),并将第二个参数设置为2。在上述示例中,round()函数调用返回的结果为9.09。这是一个浮点数,但我们需要的是字符串格式,以便在下一行代码中通过拼接来生成消息字符串。为此,我们将它传递给函数str()。Python执行这行代码的过程如下:

如果用户输入的单位不是TB、tb、GB或gb,if和elif语句中的条件都将为False,因此根本不会创建变量discrepancy。但直到Python试图使用这个不存在的变量,用户才知道出错了。此时,Python将抛出NameError: name 'discrepancy' is not defined错误,并指出错误出在给变量real_capacity赋值的代码行。

然而,出现这种bug的根本原因在于,程序无法处理用户输入无效单位的情况。对于这种错误,处理方式有很多,但最简单的方式是添加一条else语句,在其中显示类似于“You must enter TB or GB”这样的消息,并调用函数sys.exit()来退出程序(这个函数将在第3章介绍)。

这个程序的最后一行代码显示实际的硬盘容量,方法是将变量real_capacity和unit包含的字符串拼接起来,生成一个消息字符串:

print('The actual capacity is ' + real_capacity + ' ' + unit)

实际上,硬盘和闪存制造商的虚假宣传程度更严重:在我的笔记本计算机中,有一个用于备份的256 GB SD卡。如果按真实的GB计算,容量应为274 877 906 944字节;即便按虚假的GB计算,也应该有256 000 000 000字节,但我的计算机报告说,实际容量为255 802 212 352字节。有趣的是,真实容量比宣传的容量还要小。

2.9 小结

通过使用结果为True或False的表达式(也称为条件),可以让程序做出决定:执行哪些代码及跳过哪些代码。这些条件是使用比较运算符==、!=、<、>、<=或>=对两个值进行比较且结果为布尔值的表达式。还可以使用布尔运算符and、or或not将表达式合并为更复杂的表达式。Python使用缩进来标识代码块。在本章中,在if、elif和else语句中使用了代码块,但正如你将看到的,在其他Python语句中,也可使用代码块。本章介绍的流程控制语句使你能够编写出更聪明的程序。 JS3dvFT4+E7wkH2U12rwx3u5ZZLc100xgcIcl3TdoAZXH7REapMXlQfFdJnIZseh

点击中间区域
呼出菜单
上一章
目录
下一章
×