吃透Python-第 3 章 程序流程之分支
本章,我将围绕用于控制程序分支结构的 if 语句,介绍运算符、表达式、程序结构和编码规范等内容。
if 语句(if 代码块、elif 代码块与 else 代码块)
缩进
流程图
比较运算符(<、>、<=、>=、==、!=)
pass 语句
逻辑型
真和假/True 和 False
表达式和求值
逻辑运算符(and、or、not)
短路求值
通过集合来判断
条件运算符(if~else)
嵌套的 if 语句
简单语句和复合语句/代码组
min 函数和 max 函数
排序(升序/降序)和 sorted 函数
token、关键字、标识符、运算符、分隔符、字面量
语法错误和异常
编码规范和 PEP 8
3-1 if 语句
if 语句可以根据给定条件是否成立来决定是否执行处理操作。本节,我们会学习 if 语句和基本的运算符。
if 语句(其一)
代码清单 3-1 的程序在读取通过键盘输入的数值后,如果值为正数,就会打印输出“该值为正数。”
另外,在这个程序中,print 的前面有 4 个空格。它们是一定要有的。像这样在一行代码的开头插入空格的做法称为缩进(indent)。
▶ 实际上,空格的数量也可以不是 4 个(但至少要有 1 个)。另外,空格也可以替换为制表符,但我不推荐这种做法。除此之外,还要注意不能使用全角符号的空格。第 81 页会对此进行详细介绍。

首先,读取通过键盘输入的整数并将其存储至变量 n。
程序代码中的蓝色底纹部分用于判断 n 是否为正数(即是否大于 0),并在判断成立时执行打印输出操作。
这部分就叫作 if 语句(if statement),其形式如下图所示。

对 if 和冒号中间的判断表达式进行求值,如果结果为真,冒号之后的语句就会被执行。也就是说,开头的 if 表示“如果”。
该程序的判断表达式为 n > 0。在使用运算符 > 时,如果左操作数的值大于右操作数的值,则表达式结果为真(True),否则为假(False)。
▶ 求值、真、假、True 和 False 等术语会在后面详细讲解。
图 3-1 的流程图展示了该程序中 if 语句的处理过程。观察流程图中的线条和箭头就能明白程序的流程了。
▶ 第 72 页将介绍流程图的符号。

图 3-1 代码清单 3-1 中的 if 语句的流程图
在运行示例中,判断表达式 n > 0 求值后的结果为 True。程序沿流程图中“是”一侧的线路执行了以下语句,输出“该值为正数。”。

另外,像运行示例 那样,如果 n 的值小于等于 0,程序则沿“否”一侧的线路运行,不执行该语句。也就是说,屏幕上不会显示任何内容。
要点 如果想在某项条件成立时执行处理操作,可以使用 if 语句实现。
比较运算符
像 > 那样,对左操作数的值和右操作数的值进行比较的运算符称为比较运算符(comparison operator)。比较运算符共有 6 种,具体如表 3-1 所示。
表 3-1 比较运算符

▶ 程序可以连续使用多个比较运算符,本书第 59 页对此进行了讲解。
因为比较运算符的两个操作数可以是不同类型,所以即便是不同类型的整数和浮点数,也可以使用 > 或 == 进行比较。
▶ 运算符 <= 和 >= 中的 = 不能与相应的 < 和 > 颠倒位置。另外,> 和 =,以及 < 和 = 之间不能有空格。
if 语句(其二:使用 else 代码块)
前面的程序在读取的值不为正数时会直接退出,不显示任何内容。这种处理方式不够人性化。
修改后的程序如代码清单 3-2 所示。在程序读取的值不为正数时,显示“该值为 0 或负数。”。
▶ 与前面的程序相同,每个 print 函数前必须缩进。

在该程序中,else: 之后的橘色底纹部分是新添加的内容。
蓝色底纹部分和橘色底纹部分合并后构成了此次的 if 语句。蓝色部分称为 if 代码块,橘色部分称为 else 代码块。

▶ 前面程序的 if 语句仅由 if 代码块构成。
else 表示“如果不是××,则××”。判断表达式在求值后,如果结果为真则执行 if 代码块中的语句,否则(结果为假)执行 else 代码块中的语句。
因此,如图 3-2 所示,程序要根据 n 是否为正数执行不同的处理。
要点 使用带 else 代码块的 if 语句,可以根据条件的真假执行不同的处理。
▶ 使用这种形式的 if 语句时,程序必定会执行两个语句中的一个,也就是不可能两个语句都执行或都不执行。

图 3-2 代码清单 3-2 中 if 语句的流程图
判断是否相等
下面,我们来编写一个读取用键盘输入的两个整数,并判断它们是否相等的程序。请看代码清单 3-3。

程序将整数赋给变量 a 和变量 b,然后判断变量的值是否相等。
在 if 语句的判断表达式中,== 是比较运算符,用于判断左操作数和右操作数是否相等(表 3-1)。
▶ 用于整数和浮点数等值判断的 37==37.0 的结果是 True,而用于整数和字符串等值判断的 37== '37' 的结果是 False。
代码清单 3-4 使用运算符 != 判断左操作数和右操作数是否相等。

请注意,两个语句插入的顺序发生了变化。
因为 if、判断表达式、冒号“:”和 else 都是独立的单词,所以表达式和 : 之间,以及 else 和 : 之间即使插入空格也不会产生错误,但一般不会这样做。
▶ 第 81 页讲解的编码规范建议逗号、冒号和分号之前不插入空格。
if 语句(其三:使用 elif 代码块)
代码清单 3-5 演示了如何判断整数的符号,即判断整数是正数、0,还是负数。

这次用的 if 语句在 if 代码块和 else 代码块之间添加了形式为“elif 表达式 : 语句”的 elif 代码块。

elif 是 else if 的缩略形式,表示“如果不是××,那么如果××”。
与之前的 if 语句相同,各个代码块中的语句执行后,if 语句也执行完毕(不再执行后面的代码块)。
因此,程序只会执行 3 个 print 函数调用的其中一个,不会出现都不执行或执行多个的情况。
另外,各个代码块的处理(print 函数调用)完成后,if 语句执行完毕。
要点 使用带有 elif 代码块的 if 语句可以在不同条件成立时执行不同的处理。
最后一行代码在程序的最后调用了 print 函数打印输出“程序退出。”该行代码与 if 语句无关。没有缩进的语句与之前的 if 语句无关。
▶ 试着像下面这样,在最后的 print 函数前执行与上一行代码相同的缩进(chap03/list0305a.py)。
if n > 0:
print('该值为正数。')
elif n == 0:
print('该值为0。')
else:
print('该值为负数。')
print('程序退出。')
这样一来,只有当 n 为负数时程序才会打印输出“程序退出。”下一节我将详细讲解缩进的含义与使用方法。
多个 elif 代码块
代码清单 3-6 演示了使用多个 elif 代码块的情形。

使用过喷墨打印机的人应该对 CMYK 比较熟悉。上面这个程序就是根据输入的数字 0~3,打印输出 CMKY 各字母所代表的颜色。虽然有 3 个 elif 代码块,但这里并没有 else 代码块。因此,如果输入 0~3 以外的数字,程序最终什么都不会显示。
如运行示例所示,程序流程有 5 个分支,而不是 4 个。
▶ 有些编程语言支持实现了多分支结构的 switch 语句,但 Python 并不支持。
罗列 if 语句
请看代码清单 3-7 所示程序。

该程序有两个并列的(没有 elif 代码块和 else 代码块的)if 语句,它们按顺序执行,所以只在各自的判断表达式为真时程序会执行打印输出。
并列使用的两个 if 语句与带有 elif 代码块或 else 代码块的 if 语句完全不同。注意不要混淆二者。
▶ 第 68 页的内容介绍了该程序被稍加修改后是如何执行的。
pass 语句
我们回到代码清单 3-5 的程序上。该程序虽然有 3 个分支分别输出不同的符号,但 0 从本质上很难说是符号。
当 if 语句的各个代码块不需要执行操作时,我们可以用 pass 语句来跳过该代码块的执行。具体如代码清单 3-8 所示。

只由 pass 构成的 pass 语句(pass statement)不执行任何操作。它的应用很广泛,除在 if 语句中使用以外,还未确定要执行什么操作(想要妥善决定后再写代码),以及想要明确表示不执行任何操作时都可以使用。
要点 在语法结构上必须有语句的位置,如果不需要进行任何操作,则放置 pass 语句。
▶ 因为必须放置语句,所以不能用注释来代替 pass(虽然有的图书和网络资料中介绍注释是语句,但其实注释并不是语句)。
简单语句
这里讲解的 pass 语句是一种简单语句(simple statement)。以下为常用的简单语句。
表达式语句(expressionstatement)
调用的表达式直接变为语句。 print(‘ABC’)
赋值语句(assignmentstatement)
进行赋值操作的语句(第 1 章介绍过赋值语句)。 a = b
简单语句还包括 break 语句、continue 语句和 del 语句等。
逻辑型(bool 型)
逻辑型(又称 bool 型,bool type)表示真和假。逻辑型包含两个值,如果是假则为 False,如果是真则为 True。False 和 True 在 Python 内部分别用 0 和 1 表示。
Python 中所有的值都根据以下规则判断真假。
False、0、0.0、None、空值(包括空字符串 ''、空列表 []、空元组 ()、空字典 {}、空集合 set())都为假。
除此以外的值和 True 都为真。
▶ 第 5 章会介绍 None,第 7 章会介绍列表,第 8 章会介绍元组、字典和集合。
也就是说,虽然 False 为假,但被视为假的值(0 或空字符串等)不一定是 False。同样,True 为真,但被视为真的值不一定是 True。
逻辑值(逻辑型的值)转换为字符串后会变为 ‘False’ 或 ‘True’。另外,使用 print 函数输出逻辑值后,程序会显示出字符串。
具体请看代码清单 3-9。

在程序的前半部分,表达式使用比较运算符对整数 a 和整数 b 进行比较,并打印输出相应的结果。真和假自动转换为字符串,程序打印输出 ‘True’ 或 ‘False’。
程序的后半部分打印输出了 False、True 和 True+5 的值。逻辑型的值与整数相加是实际编程时常用的技巧。
要点 在 Python 内部,逻辑值为 False 时用 0 表示,为 True 时用 1 表示。另外,逻辑值转换为字符串后会分别变成 'False' 和 'True'。
表达式和求值
本章使用了表达式和求值等术语,我们来好好理解一下这些术语。
表达式
前面的内容频繁使用了术语表达式(expression)。我们可以把表达式理解为以下三项内容的总称。
变量
字面量
用运算符结合变量和字面量的项
以 no + 135 为例,变量 no、整数字面量 135 和使用运算符 + 将二者结合的 no + 135 都是表达式。
另外,“○○运算符”和操作数结合在一起的表达式称为“○○表达式”。比如,使用比较运算符 > 将 no 和 135 结合在一起的表达式 no > 135 称为比较表达式。
求值
表达式中一般包含类型和值。其中,值会在程序运行时被解析,解析的过程则称为求值(evaluation)。
图 3-3 是求值的具体图例。变量 no 是 int 型,值是 52。

图 3-3 表达式求值
因为变量 no 的值是 52,所以如图 3-3 所示,表达式 no、135 和 no+135 在求值后分别为 52、135 和 187。当然,三者都为 int 型。
这里,我们用类似于电子温度计的图来显示求值结果。左侧的小字是类型,右侧的大字是值。
要点 表达式包含类型和值。程序在运行时会对表达式进行求值。
注意,表达式整体的类型和各个操作数的类型不一定是相同的。
图 3-3 … int > int
当 int 型变量 no 的值为 52 时,表达式 no > 135 在求值后得到逻辑型(bool 型)的 False。
图 3-3 … bool + int
前面讲解过该表达式。因为逻辑型的 True 在 Python 内部用整数 1 表示,所以将其与 5 进行加法运算后,结果是 int 型的 6。
我们以三个表达式为例,学习了求值、类型和值,下面来实际运行一下代码清单 3-10。

上面的代码使用了第 16 页介绍的 type 函数来查看类型。
另外,打印输出时使用了上一章介绍的 format 方法(用于向格式化的字符串中插入内容)。
逻辑运算符
我们来编写一个读取整数后,判断整数是 0、一位数,还是多位数的程序。该程序如代码清单 3-11 所示。

逻辑与运算符 and
代码清单 3-11 中的蓝色底纹部分用于判断读取的值是否为一位数。
基于运算符 and 的表达式“x and y”相当于中文的“x 且 y”(图 3-4 )。因此,当 n 大于等于 -9 且小于等于 9 时,程序会打印输出“该值为一位数。”。
▶ 因为比较运算符 >= 和 <= 比运算符 and 的优先级高,所以蓝色底纹部分的判断能正确执行。本书第 76 页的表 3-5 是运算符一览表,该表包含了各个运算符的优先级。
另外,当 n 等于 0 时,程序输出“该值为 0。”后会结束 if 语句,所以当 n 为 -9, -8,…, -2, -1, 1, 2,…, 8, 9 中的一个数时,程序会输出“该值为一位数。”。
逻辑或运算符 or
下面我们来编写一个在读取数字后,判断其是否为多位数,然后打印输出相应内容的程序。该程序如代码清单 3-12 所示。

基于运算符 or 的表达式“x or y”相当于中文的“x 或者 y”(图 3-4 ),但是它表达的含义是“任意一个成立”,而不是“只有一个成立”。
因此,只有当变量 n 的值小于等于 -10 或大于等于 10 时,程序才会打印输出“该值为多位数。”。
逻辑非运算符 not
现在,我们来思考如何反转上面这个程序的判断结果。反转了逻辑值结果(图 3-4 )的程序如代码清单 3-13 所示,改写时使用的是运算符 not。

该程序的橘色底纹部分对上一程序蓝色底纹部分的结果取非。当变量 n 的值大于 -10 或小于 10 时,表达式的求值结果为真,程序打印输出“该值不是多位数。”。
前面我们的这三个运算符统称为逻辑运算符。
实际上,图 3-4 展示了普通情况下的逻辑与、逻辑或和逻辑非各自的运算结果。比较容易让人混淆的是 Python 的运算符 and 和运算符 or,它们与下图所示的普通逻辑运算不同。

图 3-4 普通情况下的逻辑与、逻辑或和逻辑非各自的真值表
表 3-2 是 Python 的逻辑运算符概要,其中只有运算符 not 可以生成 True 和 False。大家要正确理解 Python 的逻辑运算符。
表 3-2 逻辑运算符

▶ 逻辑运算符按优先级从高到低的顺序依次为 not、and 和 or。
逻辑运算表达式的求值和短路求值
首先,我们通过代码清单 3-14 来理解一下逻辑与运算符 and 的运算行为。
▶ 注意,对红色点线包围的表达式求值后的结果会赋给变量 c。

前面的表 3-2 已经展示过,运算符 and 的运算行为如下所示。
如果 x 为假,则结果为 x 的值;如果 x 为真,则结果为 y 求值后的结果。
我们现在通过各运行示例来验证结果。
运行示例 (b 的值为 0 ➨ 左操作数 x 为假)
如果程序确认左操作数 b!=0 为假,则 and 表达式的求值过程结束(c 最终被赋值为 False)。这是因为如果左操作数为假,则逻辑与 and 的结果一定为假。也就是说,运算符 and 的左操作数求值结果如果为假,则跳过右操作数的求值过程。
为了避免除数为 0 而产生运行时错误,右操作数 a % b 的求值过程被省略。
运行示例 (b 的值为 4 ➨ 左操作数 x 为真)
因为左操作数 b!=0 为真,所以程序会对右操作数 a % b 进行求值。12 除以 4 的余数 0 被赋给了 c。
运行示例 (b 的值为 5 ➨ 左操作数 x 为真)
因为左操作数 b!=0 为真,所以程序会对右操作数 a % b 进行求值。12 除以 7 的余数 5 被赋给了 c。
综上所述,c 的赋值情况如下所示。
如果 b 等于 0:逻辑值 False。 ※注意:除法运算 a % b 未被执行。
如果 b 不等于 0:整数 a 除以 b 的余数。
运算符 or 的运算行为如下所示。
如果 x 为真,则结果为 x 的值;如果 x 为假,则结果为 y 求值后的结果。
详见代码清单 3-15 所示程序。

像运行示例 那样,程序在确认 b == 0 成立后结束运行。左操作数如果为真,则逻辑或 or 的结果一定为真。也就是说,如果运算符 or 的左操作数的求值结果为真,程序会跳过右操作数的求值过程。
如运行示例 所示,如果 b == 0 不成立,程序会对右操作数求值。这时,程序执行右操作数 print('a // b = ', a // b),并打印输出相应内容。
左操作数的求值结果一经确定即跳过右操作数的求值过程。这种逻辑运算的求值称为短路求值(short circuit evaluation)。
要点 因为运算符 and 和运算符 or 会进行短路求值,所以右操作数不一定会被求值。不论是哪个运算符,最终生成的值都是最后求得的表达式的值。该值不一定是逻辑值的真和假。
也就是说,Python 的逻辑运算符会按照图 3-5 的方式进行运算。

图 3-5 Python 的 and 运算符、or 运算符和 not 运算符
多重比较
代码清单 3-16 使用逻辑与运算符 and 和逻辑或运算符 or,根据月份判断季节。

该程序使用运算符 and 和运算符 or 来判断季节,并且仅使用一行代码就完成了各类判断。但是,如果执行判断的逻辑非常复杂,仅用一行代码就不够了。在编写较长的表达式时可以用 () 包围多行代码。
代码清单 3-17 利用该特性判断读取的月份是否属于冬天。

这样一来,在 () 内编写注释也变得更加容易。
要点 无法用一行代码实现的复杂表达式,可以通过 () 用多行代码来实现。
▶ () 既方便编写代码,又能记录注释,比第 1 章介绍的 \ 更加好用。另外,与 \ 一样,在使用 () 时不能在单词(变量名和运算符等)中间进行换行。
实际上,程序不使用运算符 and 也可以根据月份判断季节。具体如代码清单 3-18 所示。
比较运算符(表 3-1)可以连续使用,其效果与使用 and 连接多个表达式的效果相同。比如,x < y <= z 相当于 x < y and y <= z。
▶ 因此,在 x < y <= z 中,如果 x < y 为假,表达式 z 的求值过程就会被跳过。

▶ 比较运算符 == 也可以连续使用。判断三个变量 a、b 和 c 的值是否都相等的程序如下所示(chap03/list03a1.py)。
if a == b == c:
print('三者均相等。')
else:
print('三者并不都相等。')
连续使用比较运算符可以使代码变得更加简洁。
要点 在必要的情况下,可以连续使用比较运算符,以使代码更加简洁。
使用集合进行判断
前面介绍的那些程序,读者都需要仔细阅读,才能理解用于判断季节的各个表达式。下面,我们来看一个更为直观的程序(代码清单 3-19)。

即使是不知道正确语法规则的人,在阅读这段代码后也能大致理解这个程序吧?程序中使用 {} 包围被逗号隔开的各项数据,这种表示数据的方式称为集合。
一般来说,“a in 集合”用来判断 a 是否包含在集合中。
▶ 第 8 章会详细介绍集合。用于判断归属性的运算符 in 会在第 6 章、第 7 章、第 8 章中讲解。
条件运算符
代码清单 3-20 是读取两个数字并打印输出较小数字的程序。

程序读取变量 a 和变量 b 的值后进行比较,如果 a 的值小于 b 的值,则将 a 赋给变量 min2,否则将 b 赋给变量 min2。最后在 if 语句执行完毕后,变量 min2 的值为较小的数字。
▶ 如果 a 的值和 b 的值相同,则赋给变量 min2 的值为 b。
因为程序的最后一行在调用 print 函数时没有在开头添加缩进,所以该行代码与 if 语句没有关系。不论变量 a 和变量 b 的大小关系如何,程序都会执行该行代码。
条件运算符
前面的这个程序不使用 if 语句也可以实现,请看代码清单 3-21。

首次出现的 if else 是表 3-3 中的条件运算符(conditional operator),使用该运算符的表达式的求值过程如下一页的图 3-6 所示。
如果 a 小于 b,则将 a 赋给变量 min2,否则将 b 赋给变量 min2。条件表达式是将 if 语句凝练后的表达式。
表 3-3 条件运算符
x if y else z 如果 y 的值为真,则对 x 求值并将其作为表达式的结果,否则对 z 求值并将其作为表达式的结果。
▶ 条件运算符是唯一的三元运算符,其他运算符是一元运算符或二元运算符。
另外,如果 y 的值为真,则跳过 z 的求值过程,如果 y 的值为假,则跳过 x 的求值过程,即进行短路求值。
差值计算
代码清单 3-22 在代码清单 3-21 的基础上进行了修改,是一个求读取的两个数字之差的程序。

该程序用于计算较大数字减去较小数字后的差值。
要点 活用条件运算符可以使根据条件真假生成不同结果值的表达式更加简洁。
代码清单 3-23 嵌套使用了两个条件运算符。程序会打印输出读取的整数符号。

▶ 运行结果省略。
注意,这种程度的嵌套尚且算好懂,但若嵌套过多,程序就会变得难以阅读。

图 3-6 条件表达式的求值
3-2 嵌套的 if 语句和代码组
上一节介绍了 if 语句的基础内容。本节,我们来学习复杂的 if 语句,包括嵌套的 if 语句和 if 语句下执行多条语句的情况。
嵌套的 if 语句
先试着写一个下面这样的程序。
如果读取的整数为正数,则打印输出“偶数”或“奇数”。
如果读取的整数不为正数,则输出相应信息。
写完的程序如代码清单 3-24 所示。

如图 3-7 所示,该程序使用了嵌套结构,在 if 语句中插入了 if 语句。
因为从语法结构上来看 if 语句是单独的语句,所以外层 if 语句的 if 代码块执行的是内层的 if 语句。该 if 语句根据 n 为偶数还是奇数来执行语句 或 。
外层 if 语句的 else 代码块执行了语句 。

图 3-7 嵌套的 if 语句
另外,如果内层 if 语句中的“if n % 2 == 0:”和“else:”缩进没有对齐,程序就无法进行解析,进而产生错误。
条件成立时,if 代码块、else 代码块和 elif 代码块分别在应该执行的语句前插入 4 个空格作为缩进。另外,执行的语句也可以放在冒号与换行符之间。
具体示例如图 3-8 所示。左右两种编写代码的方式均可。

图 3-8 if 语句的编写方式
因为与后面的内容有关,所以我在这里先简单讲解一下复合语句(compound statement)。
本章讲解的 if 语句,与下一章讲解的 while 语句和 for 语句不是前面我们说过的简单语句,而是复合语句。
在这些复合语句中,被控制的语句(条件成立时执行的语句)如果是简单语句,就可以写在冒号之后的同一行内。
要点 复合语句控制的简单语句可以直接放在冒号之后,而不是放在下一行。
也就是说,代码清单 3-24 中的 if 语句就可以改写为如下形式(chap03/list0324a.py)。
if n > 0:
if n % 2 == 0: print('该值为正偶数。')
else: print('该值为正奇数。')
else:
print('输入的值不为正数。')
如果使用条件运算符,代码还可以简化为如下形式(chap03/list0324b.py)。
if n > 0:
print('该值为正{}。'.format('奇数' if n % 2 else '偶数'))
else:
print('输入的值不为正数。')
if 语句下执行多条语句
下面,我们再来写一个程序——读取两个整数,并求出其中较大的数字和较小的数字。具体如代码清单 3-25 所示。

程序中的 if 语句在 a 小于 b 时执行 ,否则执行 。if 代码块和 else 代码块各自控制了两个(应当执行的)语句。
像这样,在控制多个语句时,必须对齐这些语句的缩进,否则会产生错误。
要点 在使用复合语句控制多个语句时,这些语句的代码行必须统一缩进。
不过,如图 3-9 所示,if 代码块、elif 代码块和 else 代码块中的代码缩进只要在各自的代码块中统一即可,无须跨代码块统一。
当然,为了避免造成混淆,即使在跨代码块的情况下,缩进也应统一。

图 3-9 if 语句与缩进
由复合语句控制的多个并列语句称为代码组(suite)。
如果代码组由多个简单语句构成,各个语句要用分号隔开并编写在同一行内。代码清单 3-26 就利用了该特性。

各代码块中执行的代码组均使用了一行代码编写。
▶ 代码组表示一整组代码。另外,方框内的“其三”是整个代码块只用了一行代码编写的情况,由于代码过分凝练,反而变得不容易让人读懂了。
如果使用条件运算符,再一次性对多个变量进行赋值,我们就能用一行代码实现前面这个程序(代码清单 3-27)。

另外,如果等号右边的表达式 (a, b) 和 (b, a) 省略掉圆括号 (),表达式之间的界线就会变得不明显,进而导致错误发生。
▶ 第 8 章会介绍圆括号 () 的含义。
min 函数和 max 函数
Python 提供了内置的 min 函数和 max 函数来计算最小值和最大值。利用这两个函数就可以简化程序,请看代码清单 3-28。

min 函数和 max 函数会分别返回给定数字中的最小值和最大值。另外,参数的个数不仅限于两个,而是可以任意个。
▶ 如果把调用函数的表达式放在等号右边进行赋值,函数的返回值就会赋给等号左边的变量。第 9 章会详细讲解该过程的原理。
if 语句与缩进
下面,我们来看一看代码清单 3-29 的这个程序。该程序虽然只是在代码清单 3-7(第 51 页)中的第二个 if 语句前添加了缩进,但程序的行为发生了变化。

代码清单 3-7
两个 if 语句虽然排列在一起,但它们之间没有任何关系。因此,不论 n 的值是否为正数,程序都会去判断 n 是否为奇数。如果 n 为奇数,则输出相应的内容。
代码清单 3-29
第二个 if 语句被插入到最开始的(外层的)if 语句中。因为在 n 为正数时程序执行的是橘色底纹部分,所以只有在 n 为正数时程序才会判断它是否为奇数。如果 n 为奇数,则输出相应的内容。
▶ 也就是说,如果 n 小于 0,则不对 n 是否为奇数进行判断和输出结果(运行示例 和运行示例 )。
外层的 if 语句所控制的代码组(判断表达式 n > 0 为真时执行)由 和 两个语句构成。

因此, 和 必须对齐,而且与最开始的 if 语句相比,要再缩进一级。
▶ 在代码清单 3-29 中, 由 的 if 语句中的 if 代码块控制,放在头部的下一行。因此, 又缩进了一级。
if 语句的结构和代码组
前面用图解的方式对 if 语句的形式进行了介绍。第 50 页讲解时使用了图 3-10 中的 ,但其实图 3-10 是不准确的。图 3-10 是更加准确的语法结构。

图 3-10 if 语句的语法结构
复合语句中各代码块的起始部分以 if 或 else 开头,以冒号结尾。该部分称为各代码块的头部(header)。
头部末尾的冒号表示这之后的内容是代码组。
代码组的编写方式如下所示。
头部的下一行要再缩进一级(多使用一些空格),然后编写代码。
当代码组内有多个语句时,这些语句全部使用相同级别的缩进。
另外,这些语句可以是简单语句,也可以是复合语句。如果这些语句是复合语句,就会形成一种嵌套结构,即在复合语句中使用复合语句,具体示例如代码清单 3-29 所示。
▶ 至少有一个空格用于缩进。
只有在代码组仅由简单语句构成时,代码组可以与头部在同一行(即代码组在冒号和换行符之间)。如果有多个单纯语句,则使用分号隔开各语句。最后一个语句之后也能有分号。
if a < b: min2 = a
if a < b: min2 = a; max2 = b;
▶ 如果代码组内有复合语句,则不能与头部放在同一行。因此,执行以下代码会发生错误。
二值排序
代码清单 3-30 是读取两个整数后,按升序对其进行排列的程序。
▶ 升序表示数值按从小到大的顺序排列,降序则相反。

更改排列顺序称为排序(sort)。二值排序交换了变量 a 的值和变量 b 的值。不过,只有在 a 的值大于 b 的值时才对二者进行交换。
该程序按照图 3-11 所示步骤对两个值进行了交换。
▶ 将 a 的值保存至 t。
将 b 的值赋给 a。
将保存在 t 中的 a 的初始值赋给 b。

图 3-11 交换两个值的步骤
另外,代码清单 3-31 为了简化代码,使用了第 18 页介绍的给多个变量同时赋值的方法。将 b 赋给 a 和将 a 赋给 b 的操作(在理论上)是同时进行的。

三值排序
代码清单 3-32 是对三个数值进行升序排列的程序。

图 3-12 中使用了三个 if 语句进行三值排序。
比较 a 和 b 的值。如果左侧 a 的值大于右侧 b 的值,则交换二者的值。
对 b 和 c 进行同样的操作(只在必要时交换 b 和 c 的值)。
经过右图所示的角逐后,通过执行上述两个步骤,三个值中的最大值被保存在了 c 中。
最大值被保存在 c 中。最后进行的是决定第二名的“复活赛”。执行该 if 语句后,a 和 b 中较大的值会保存在 b 中。

图 3-12 三值排序的步骤
用于排序的内置函数 sorted
我们已经编写程序实现了二值排序和三值排序。四值排序的实现就比较费事了,因为必须列出大量的 if 语句。
了解排序的原理对于学习编程来说是必要的,但在实际编写程序时,人们一般会使用内置函数来实现排序。
代码清单 3-33 使用内置函数 sorted 改写了二值排序的程序。

第 7 章会介绍这个程序里使用的列表,当前我们没有必要彻底弄清楚它是什么。
▶ sorted 函数与其说是用于排序的函数,不如说是用于返回排序后新生成的列表的函数。当前阶段,我们只要能模仿使用这个函数就足够了。
使用 sorted 函数后,三值排序(chap03/sort03.py)的程序和四值排序(chap03/sort04.py)的程序如下所示。
a, b, c = sorted([a, b, c]) # 按照升序排列三个数值
a, b, c, d = sorted([a, b, c, d]) # 按照升序排列四个数值
另外,如果要按照降序排列,就需要给 sorted 函数传递第二个参数,参数设为 reverse=True。具体请见代码清单 3-34。

执行程序后,数值按照预想实现了降序排列。
流程图
下面,我来说说流程图(flowchart)。流程图用图来表示定义、分析和解答问题的过程,这里我们要学习流程图的基础术语和符号。
程序流程图(program flowchart)
程序流程图由以下部分构成。
表示实际运算的符号。
表示控制流程的线条符号。
帮助理解和制作程序流程图的特殊符号。
数据(data)
表示不指定媒介的数据。

处理(process)
表示任意种类的处理功能。比如,为了改变数据的值、类型和位置而执行的运算或运算群,以及在多个程序流程中为决定后继方向而执行的运算或运算群。

预定义处理(predefined process)
表示子程序或模块等在他处定义的多个运算或命令群。

判断(decision)
该符号表示的功能是在有一个入口和多个出口的情况下,按照符号中定义的条件求值,并根据其结果判断选择唯一的出口。该符号也能表示选择开关的功能。
预想的求值结果写在表示路径的线条旁边。

循环条件(loop limit)
循环条件由两部分组成,分别表示循环的开始和结束。符号的两个部分使用相同的名字。
循环的开始符号(先判断再循环)或结束符号(先循环再判断)中记录了循环的初始化方法、递增方式和结束条件。

线条(line)
表示控制的流程。
如果需要明确表示流程的方向,则线条必须添加箭头。
在不需要明确表示流程的方向时也可以添加箭头,方便辨识。

端点符(terminator)
表示进入外部环境的出口或从外部环境进来的入口。比如表示程序流程的开始或结束。

除此之外,还有并行处理和虚线等符号。
3-3 程序的构成要素
本节,我们会学习程序的各种构成要素,包括关键字、标识符、字面量和运算符等。
程序的构成要素
在 Python 程序中,行(换行符)和缩进(空格)都被赋予了明确的含义。
前面已经讲解过以下内容。
① 在代码行末尾使用 \ 可以延续本代码行至下一行。
② 空行实际上被程序忽略了。
③ 缩进的含义和使用方法。
④ 代码可以在 () 中随意换行。
关于④,这里需要补充一点内容。不仅是 ( ),在第 6 章以后讲解的 [] 和 {} 中,代码也可以随意换行。也就是说,
要点 在 ()、[]、{} 中,代码可以用多行记述。
程序的构成要素中除了有换行和空格,还包括关键字、标识符、注释、运算符和分隔符等。
这些要素称为 token,类似于中文里的“单词”。
▶ 第 30 页已经介绍过注释,不过第 9 章还会介绍注释的相关内容。
关键字
本章介绍的 if 和 else 等有特殊含义的词语称为关键字(keyword)。表 3-4 是关键字一览表。
表 3-4 关键字一览表

▶ 关键字也称为保留字(reservedword)。
标识符
变量、函数和类可以使用的名称叫作标识符(identifier)。
语法上允许使用汉字作为构成标识符的字符,但我不推荐这种做法。标识符一般由以下字符构成。
大写字母 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
小写字母 a b c d e f g h i j k l m n o p q r s t u v w x y z
下划线 _
数字 0 1 2 3 4 5 6 7 8 9
标识符的起始字符不能使用数字。另外,关键字不能作为标识符使用。
正确示例 x1 _x1 abc _
错误示例 1x if
▶ 表 6-2(第 149 页)中介绍的 str.isidentifier 方法可以用来查看字符串是否可以作为正确的标识符使用。
以 __ 开头的名称和以 __ 开头并以 __ 结尾的名称具有特殊含义,这些名称叫作保留的标识符类型(reserved classes of identifiers)。
运算符
表 3-5 是运算符(operator)一览表。
优先级
第 1 章介绍过运算符的优先级(precedence)。该表中的运算符分成了 17 组并按照优先级从高到低的顺序排列。
结合规则
大部分运算符是从左往右进行运算的,这种运算方式称为左结合。比如 24 // 4 // 2 的运算结果为 3。
▶ 如果运算符是从右往左进行运算的,则上述表达式会被程序视为 24 // (4 // 2),运算结果变为 12。
求幂运算符 ** 是唯一例外的右结合运算符。
▶ 比如,2 ** 1 ** 4 被视为 2 ** (1 ** 4),其运算结果为 2。
赋值符号不是运算符
= 和 += 等符号具备多种赋值功能(包括下一章讲解的增量赋值),但它们并不是运算符。
运算符的优先级、名称和结合规则如表 3-5 所示。
表 3-5 所有运算符的优先级、名称和结合规则


分隔符
标识符和关键字等各个 token 之间一般需要留空。比如“if a == 0:”中的 if 和 a 不能连起来写成“ifa == 0:”。
但是,如果使用了分隔符(delimiter),各个 token 前后就不需要留空了。比如刚才的例子,如果使用了分隔符 (),则可以写成“if(a==0):”。
具体如下所示。

表 3-6 为分隔符一览表。
表 3-6 分隔符一览表

▶ “.”也会用在浮点数字面量和虚数字面量中。此外,“.”也会作为省略符号“...”使用。在这些情况下,“.”并不作为分隔符使用。
数值字面量
在第 1 章中,我们学习了整数字面量和浮点数字面量。包括这两种字面量在内,一共有三种数值字面量。
整数字面量
整数字面量用于表示 int 型的整数,它存储在主存中且没有位数限制。
第 1 章讲过,Python 从版本 3.6 开始,可以在十进制字面量、二进制字面量、八进制字面量和十六进制字面量的任意位置插入下划线。
下面就是整数字面量的示例。
7 2147483647 0o177 0b100110111
3 79228162514264337593543950336 0o377 0xdeadbeef
100_000_000_000 0b_1110_0101
▶ 我们也学习过非 0 的十进制字面量不能以 0 开头。
浮点数字面量
用十进制数表示 float 型实数的字面量称为浮点数字面量。
下页是浮点数字面量的示例。
3.14 10. .001 1e100 003.14e-010 0e0 3.14_15_93
另外,与十进制整数字面量不同,在浮点数字面量的情况下,不论是整数部分还是指数部分,开头都可以有多余的 0。
▶ 因此,3.14e-10 与上例中的 003.14e-010 的意义相同。
虚数字面量
虚数字面量(imaginary literal)可以表示实数部分为 0.0 的复数。
虚数字面量的形式是在浮点数字面量后添加 j(大写字母 J 和小写字母 j 都可以)。
下面是虚数字面量的示例。
3.14j 10.j 10j .001j 1e100j 3.14e-10j 3.14_15_93j
像 (2.3 + 5j) 这样,对浮点数和虚数字面量进行加法运算,就可以得到 complex 类型的复数。
字符串字面量和字节序列字面量
字符串字面量我们也在第 1 章学过了,后面的章节会介绍以下字面量。
格式化字符串字面量
格式化字符串字面量(formatted string literal)也称为 f 字符串,它是一种以 f 或 F 为前缀的字符串字面量。
▶ 第 6 章会对其进行详细讲解。
字节序列字面量
字节序列字面量(bytes literal)是以 b 或者 B 为前缀的(与字符串形式类似的)字面量。
▶ 第 7 章会对其进行详细讲解。
语法错误和异常
Python 中有两种错误,分别是语法错误(sytax error)和异常(exception)。
在熟练掌握 Python 语法前肯定会不断遇到各种错误。下面,我们就来了解一下常见错误和相应的规避方法。
▶ 红色文字是错误信息,绿色文字是错误信息的中文翻译,黑色文字是如何规避错误的说明。
语法错误
语法错误包括 SyntaxError 和 IndentationError,这是字句方面(包含缩进)的错误。程序如果包含语法错误则无法执行。
×
print('ABC)
SyntaxError: EOL while scanning string literal
语法错误:在解析字符串字面量时遇到 EOL(End Of Line,行尾)
字符串字面量缺少了表示结束的符号“‘”,因此需要在 C 之后添加“’”。
当字符串字面量的开始符号和结束符号不一致时,该错误也会发生。例如 'ABC" 中 ’ 和 " 混用的情况。
×
print('x的值是'. x. '')
SyntaxError: invalid syntax
语法错误:无效的语法
传递给 print 函数的实参使用了错误的分隔符,因此需要将 print 函数中的两个“.”换成“,”。
×
print('x的值是', x, '')
SyntaxError: invalid character in identifier
语法错误:标识符中含有无效字符
传递给 print 函数的实参在用逗号隔开时,逗号后插入了全角空格。这种错误虽然难以察觉,但我们只要将全角空格修改为半角空格即可。
×
m1 = 17
m2 = 08
SyntaxError: invalid token
语法错误:无效的 token
十进制整数字面量之前不能有 0,因此我们要将 08 改为 8。
▶ 没有必要为了对齐上下代码行而使用这种编写代码的方式。
×
if a > 5
print('a大于5')
SyntaxError: invalid syntax
语法错误:无效的语法
缺少 if 语句头部的“:”,因此我们要在 5 之后添加“:”。
×
if a > 5:
print('a大于5')
IndentationError: expected an indented block
缩进错误:代码块需要添加缩进
需要在 print 前添加缩进(至少要有一个空格)。
异常
异常是在程序执行时发生的错误。我会在第 12 章详细介绍异常,这里只介绍错误示例。一般仔细阅读代码即可规避此类错误。
×
x = 5 / 0
ZeroDivisionError: division by zero
零除错误:进行了以 0 为除数的除法运算
不要进行以 0 为除数的除法运算。
×
'ABC' + 5
TypeError: can only concatenate str (not "int") to str
类型错误:只有字符串可以与字符串结合(“int”不可以)
字符串不能和整数结合,因此我们要用 str(5) 把整数 5 转换为字符串。
×
a = '5.3'
print(int(a))
ValueError: invalid literal for int() with base 10: '5.3'
值错误:传递给 int() 的字面量 ‘5.3’ 不是正确的十进制字面量
‘5.3’ 不能转换为整数,因此要将字符串 ‘5’ 赋给 a,或者使用 float() 而不是 int() 进行浮点数转换。
PEP 和编码规范
PEP(Python Enhancement Proposal,Python 增强建议书)汇总了大量文档。这些文档详细说明了建议 Python 实现的功能和 Python 已经实现的功能。
Python 官网 -Documentation-PEP Index
各 PEP 以数字进行区分。其中,PEP 8“Style Guide for Python Code”(Python 编码规范)作为编码规范为人所熟知。这里,我们来了解一下 PEP 8 的部分内容。
▶ PEP 不仅详细介绍了新功能,还总结了新功能的设计过程。PEP 0“Index of Python Enhancement Proposals”一文介绍了 PEP 的整体情况。
编码规范的作用是提高代码的可读性,保持代码的一致性。不过,我们在编程时也不必过度拘泥于代码的一致性。
▶ 如果按照编码规范编写的代码难以阅读,则应当按照易于阅读的方式编写代码。
要将所有的代码行控制在 79 个字符以内,同时在适当位置插入空行。
不要在代码行末尾添加不必要的空格和制表符。
一级缩进使用 4 个空格。Python 从版本 3 开始不允许混用制表符和空格,所以大家尽量不要使用制表符。
▶ 在 Chromium 项目(TheChromiumProjects)中,Python 风格指南(Python Style Guidelines)规定缩进为两个空格,但它并不是 PEP 的编码规范。在实际编码中,我们还应当考虑遵循组织或项目的编码策略。
用于表示字符串字面量的开始与结束的符号应当统一使用“'”或“"”。
在 # 后要先插入 1 个空格,然后写注释。注释的内容不应只对代码的执行内容进行简单说明。
▶ 比如下面的注释就没有实质内容(以下注释起码对熟悉 Python 的人来说是毫无意义的)。
x = x + 1 # 递增x
但是,由于本书是入门书,所以经常会使用这样的注释。
如果要在二元运算表达式中间进行换行,应当在运算符之前换行。
○
x = (number
+ point
- (x * y))
×
x = (number +
point -
(x * y))
常量在模块一级(函数定义外)进行定义。常量一般使用大写字母。如果常量的名称由多个单词构成,则使用下划线分隔这些单词。
▶ 表示常量的变量名是 TOTAL 或 MAX_OVERFLOW 这种形式。另外,第 9 章和第 10 章将分别对函数和模块进行讲解。
关于表达式和语句中的空白字符
应当避免过多使用空格。以下位置请不要插入空格。
在使用圆括号、中括号和大括号时,在开括号后和闭括号前不要插入空格。
○ spam(ham[1], {eggs: 2}) × spam( ham[ 1 ], { eggs: 2 } )
末尾的逗号和之后闭括号之间的位置
○ foo = (0,) × foo = (0, )
紧靠逗号、分号和冒号之前的位置
○ if x == 4: print(x, y); x, y = y, x
× if x == 4 : print(x, y) ; x , y = y , x
用于指定分片的冒号前后。不过,在复杂的情况下可以在该位置插入相同数量的空格。
○ sl[1:9], sl[1:9:3], sl[:9:3], sl[1::3], sl[1:9:]
× sl[1: 9], sl[1 :9], sl[1:9 :3]
○ sl[lower:upper], sl[lower:upper:], sl[lower::step]
× sl[lower : : upper]
○ sl[lower+offset : upper+offset]
○ sl[lower + offset : upper + offset]
× sl[lower + offset:upper + offset]
○ sl[: upper_fn(x) : step_fn(x)], sl[:: step_fn(x)]
× sl[ : upper]
○ print (x)
▶ 分片将在第 6 章、第 7 章和第 8 章进行讲解。
在二元运算符前后各插入 1 个空格。
当不同优先级的运算符混用时,可以在优先级最低的运算符的两侧插入空格。
由 if 语句、while 语句和 for 语句控制的代码组与头部放在不同代码行。
▶ 比如代码清单 3-32 就没有遵循编码规范。
本书在介绍函数、模块和类等内容时,会结合编码规范进行讲解。
▶ pycodestyle 可以检查脚本程序是否遵循了 PEP8 规范,autopep8 可以按照 PEP8 规范对脚本程序进行修正。
总结
“变量”“字面量”“用运算符结合变量和字面量后的项”都是表达式。程序在执行时会通过求值获取表达式的类型和值。
逻辑型(bool 型)变量的值是逻辑值。如果逻辑为假,则逻辑值为 False,如果逻辑为真,则逻辑值为 True。程序内部逻辑值分别用 0 和 1 表示。将逻辑值转换为字符串后会得到 ‘False’ 和 ‘True’。另外,空值和 0 视为假,其他值视为真。
进行逻辑运算的 and 运算符、or 运算符和 not 运算符统称为逻辑运算符。and 运算符和 or 运算符生成的是最后表达式的值,该值不仅限于逻辑值。在使用这两个运算符进行短路求值时,可以跳过右操作数的求值过程。

比较运算符(<、<=、>、>=、==、!=)可以判断值的大小和等价关系。表达式如果使用了比较运算符,求值后会得到逻辑值的 True 或 False。另外,比较运算符可以连续使用,此时的效果与使用 and 进行结合的效果相同。
直接作为语句使用的表达式称为表达式语句。表达式语句和赋值语句又称为简单语句。
在语法结构上必须有语句的位置,如果不需要进行任何操作,则放置 pass 语句。
在使用 if 语句的情况下,我们可以根据条件是否成立来选择相应的程序流程分支进行处理。
if 语句的各代码块(if 代码块、elif 代码块和 else 代码块)由头部及其控制的代码组构成。

代码组原则上要在头部的下一行编写,并且要增加缩进级别。缩进时至少使用 1 个空格,但其实标准缩进使用的是 4 个空格。
如果复合语句以 if 语句开始,并且复合语句控制的代码组仅由简单语句构成,那么代码组可以不换行直接放在头部的冒号后面。另外,在有多个语句的情况下,各语句用“;”隔开。
由 ()、[] 或 {} 包围的地方可以编写多行代码,因此很难用一行代码编写的复杂表达式可以用 () 包围,通过多行代码编写。
三元运算符 if ~ else~ 称为条件运算符,使用条件运算符可以将 if 语句的操作凝练至单一的条件表达式中。根据两个操作数的求值结果,对第一个或第三个操作数进行求值。
可以使用内置的 min 函数和 max 函数计算多个数中的最小值和最大值。
通过 a,b = b,a 可以交换 a 和 b 的值。
对多个数按照升序或降序进行排列称为排序。我们可以使用内置的 sorted 函数进行排序。
程序的构成要素包括关键字、标识符、运算符、分隔符和字面量等。
在并列使用多个运算符时,优先级高的运算符优先执行。在连续使用相同优先级的运算符时,程序会根据结合规则按从左往右的顺序或从右往左的顺序执行运算。大多数运算符是左结合的。
Python 有两种错误类型,分别是语法错误和异常。语法错误是包含缩进在内的拼写失误。
在编写程序时,应遵循 PEP 8“Style Guide for Python Code”。
流程图是一种用图表示程序流程的方法。

3978

被折叠的 条评论
为什么被折叠?



