条件执行
布尔表达式
布尔表达式 (boolean expression) 是值为真 (true) 或假 (false) 的表达式。以下示例使用运算符 ==
,它比较两个操作数,如果它们相等则生成 True
,否则生成 False
:
>>> 5 == 5
True
>>> 5 == 6
False
True
和 False
是属于 bool
类的特殊值;它们不是字符串:
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
==
运算符是比较运算符 (comparison operators) 之一;其他的还有:
x != y # x 不等于 y
x > y # x 大于 y
x < y # x 小于 y
x >= y # x 大于或等于 y
x <= y # x 小于或等于 y
x is y # x 与 y 是同一个对象
x is not y # x 与 y 不是同一个对象
尽管你可能熟悉这些运算,但 Python 符号与相同运算的数学符号不同。一个常见的错误是使用单个等号 (=
) 而不是双等号 (==
)。记住 =
是赋值运算符,而 ==
是比较运算符。没有 =<
或 =>
这样的写法。
逻辑运算符
有三个逻辑运算符 (logical operators):and
、or
和 not
。这些运算符的语义(含义)与其在英语中的含义相似。例如,
x > 0 and x < 10
仅当 x
大于 0 且小于 10 时才为真。
n%2 == 0 or n%3 == 0
如果任一条件为真,则为真,也就是说,如果数字能被 2 或 3 整除。
最后,not
运算符否定一个布尔表达式,所以如果 x > y
为假,则 not (x > y)
为真。
>>> x = 1
>>> y = 2
>>> x > y
False
>>> not (x > y)
True
严格来说,逻辑运算符的操作数应该是布尔表达式,但 Python 不太严格。任何非零数字都被解释为“真”。
>>> 17 and True
True
这种灵活性在某些情况下可能有用,但它有一些可能令人困惑的微妙之处。在你确定自己知道在做什么之前,你可能想要避免使用它。
条件执行
为了编写有用的程序,我们几乎总是需要能够检查条件并相应地改变程序的行为。条件语句 (Conditional statements) 给了我们这种能力。最简单的形式是 if
语句:
if x > 0 :
print('x is positive')
if
语句后面的布尔表达式称为条件 (condition)。我们用冒号字符 (:) 结束 if
语句,并且 if 语句后面的行是缩进的。
If 逻辑流程图
如果逻辑条件为真,则执行缩进的语句。如果逻辑条件为假,则跳过缩进的语句。
if
语句具有与函数定义或 for
循环 1 相同的结构。语句由一个以冒号字符 (:) 结尾的标题行和一个缩进的代码块组成。像这样的语句被称为复合语句 (compound statements),因为它们跨越多行。
if x > y:
print(x)
print(y)
主体中可以出现的语句数量没有限制,但必须至少有一条。有时,有一个没有语句的主体是有用的(通常作为你尚未编写的代码的占位符)。在这种情况下,你可以使用 pass
语句来通过 Python 解释器的检查,它什么也不做。
if x < 0 :
pass # 需要处理负值,暂时什么也不做。
如果你在 Python 解释器中输入 if
语句,提示符将从三个尖括号 (>>>) 变为三个点 (...),以指示你正处于语句块的中间,如下所示:
>>> x = 3
>>> if x < 10:
... print('Small')
...
Small
>>>
使用 Python 解释器时,你必须在块的末尾留一个空行,否则 Python 会返回错误:
>>> x = 3
>>> if x < 10:
... print('Small')
... print('Done')
File "<stdin>", line 3
print('Done')
^
SyntaxError: invalid syntax
在编写和执行脚本时,语句块末尾的空行不是必需的,但它可能会提高代码的可读性。
可选执行
if
语句的第二种形式是可选执行 (alternative execution),其中有两种可能性,条件决定执行哪一种。语法如下:
if x % 2 == 0:
print('x is even')
else:
print('x is odd')
如果 x
除以 2 的余数为 0,那么我们知道 x
是偶数,程序会显示相应的消息。如果条件为假,则执行第二组语句。
If-Then-Else 逻辑流程图
由于条件必须为真或假,因此两个备选项中只有一个会被执行。这些备选项被称为分支 (branches),因为它们是执行流程中的分支。
链式条件
有时可能性不止两种,我们需要两个以上的分支。表达这种计算的一种方法是链式条件 (chained conditional):
if x < y:
print('x is less than y')
elif x > y:
print('x is greater than y')
else:
print('x and y are equal')
elif
是 “else if” 的缩写。同样,只有一个分支会被执行。
If-Then-ElseIf 逻辑流程图
elif
语句的数量没有限制。如果存在 else
子句,它必须在末尾,但并非必须要有 else
子句。
if choice == 'a':
print('Bad guess')
elif choice == 'b':
print('Good guess')
elif choice == 'c':
print('Close, but not correct')
每个条件按顺序检查。如果第一个为假,则检查下一个,依此类推。如果其中一个为真,则执行相应的分支,并且语句结束。即使有多个条件为真,也只执行第一个为真的分支。
嵌套条件
一个条件语句也可以嵌套在另一个条件语句中。我们可以像这样编写上面那个三分支的例子:
if x == y:
print('x and y are equal')
else:
if x < y:
print('x is less than y')
else:
print('x is greater than y')
外层条件包含两个分支。第一个分支包含一个简单的语句。第二个分支包含另一个 if
语句,它本身有两个分支。这两个分支都是简单的语句,尽管它们也可以是条件语句。
嵌套 If 语句流程图
尽管语句的缩进使结构清晰可见,但嵌套条件 (nested conditionals) 很快就会变得难以阅读。通常,如果可以的话,最好避免使用它们。
逻辑运算符通常提供了一种简化嵌套条件语句的方法。例如,我们可以使用单个条件重写以下代码:
if 0 < x:
if x < 10:
print('x is a positive single-digit number.')
只有当我们通过两个条件时,print
语句才会被执行。我们可以使用 and
运算符达到相同的效果:
if 0 < x and x < 10:
print('x is a positive single-digit number.')
使用 try 和 except 捕获异常
之前我们看到了一个代码段,其中我们使用了 input
和 int
函数来读取和解析用户输入的整数。我们也看到了这样做是多么危险:
>>> prompt = "What is the air velocity of an unladen swallow?\n"
>>> speed = input(prompt)
What is the air velocity of an unladen swallow?
What do you mean, an African or a European swallow?
>>> int(speed)
ValueError: invalid literal for int() with
base 10: 'What do you mean, an African or a European swallow?'
>>>
当我们在 Python 解释器中执行这些语句时,我们会从解释器那里得到一个新的提示符,心想“哎呀”,然后继续执行下一条语句。
但是,如果你将此代码放在 Python 脚本中并且发生此错误,你的脚本会立即因回溯信息而停止运行。它不会执行后续的语句。
这是一个将华氏温度转换为摄氏温度的示例程序:
inp = input('Enter Fahrenheit Temperature: ')
fahr = float(inp)
cel = (fahr - 32.0) * 5.0 / 9.0
print(cel)
# 代码: https://www.py4e.com/code3/fahren.py
如果我们执行此代码并给它无效的输入,它只会失败并显示不友好的错误消息:
python fahren.py
Enter Fahrenheit Temperature:72
22.22222222222222
python fahren.py
Enter Fahrenheit Temperature:fred
Traceback (most recent call last):
File "fahren.py", line 2, in <module>
fahr = float(inp)
ValueError: could not convert string to float: 'fred'
Python 中内置了一个条件执行结构来处理这些预期和意外的错误,称为“try / except”。try
和 except
的目的是,你知道某段指令序列可能会出问题,并且你想添加一些语句以便在发生错误时执行。如果没有错误,这些额外的语句(except 块)将被忽略。
你可以将 Python 中的 try
和 except
功能视为对一系列语句的“保险策略”。
我们可以像下面这样重写我们的温度转换器:
inp = input('Enter Fahrenheit Temperature:')
try:
fahr = float(inp)
cel = (fahr - 32.0) * 5.0 / 9.0
print(cel)
except:
print('Please enter a number')
# 代码: https://www.py4e.com/code3/fahren2.py
Python 首先执行 try
块中的语句序列。如果一切顺利,它会跳过 except
块并继续执行。如果在 try
块中发生异常,Python 会跳出 try
块并执行 except
块中的语句序列。
python fahren2.py
Enter Fahrenheit Temperature:72
22.22222222222222
python fahren2.py
Enter Fahrenheit Temperature:fred
Please enter a number
使用 try
语句处理异常称为捕获 (catching) 异常。在这个例子中,except
子句打印一条错误消息。通常,捕获异常让你有机会修复问题、重试,或者至少优雅地结束程序。
逻辑表达式的短路求值
当 Python 处理像 x >= 2 and (x/y) > 2
这样的逻辑表达式时,它从左到右对表达式求值。根据 and
的定义,如果 x
小于 2,表达式 x >= 2
为 False
,因此无论 (x/y) > 2
求值为 True
还是 False
,整个表达式都为 False
。
当 Python 检测到评估逻辑表达式的其余部分没有任何益处时,它会停止评估,并且不会执行逻辑表达式其余部分的计算。当逻辑表达式的评估因为最终值已知而停止时,这被称为短路 (short-circuiting) 求值。
虽然这看起来可能只是一个细节问题,但短路行为引出了一种称为守护模式 (guardian pattern) 的巧妙技术。考虑 Python 解释器中的以下代码序列:
>>> x = 6
>>> y = 2
>>> x >= 2 and (x/y) > 2
True
>>> x = 1
>>> y = 0
>>> x >= 2 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and (x/y) > 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
第三次计算失败了,因为 Python 正在计算 (x/y)
而 y
为零,这导致了运行时错误。但是第一个和第二个例子没有失败,因为在第一个计算中 y
非零,而在第二个计算中,这些表达式的第一部分 x >= 2
求值为 False
,因此根据短路规则,(x/y)
从未被执行,也就没有错误。
我们可以构造逻辑表达式,策略性地在可能导致错误的求值之前放置一个守护 (guard) 求值,如下所示:
>>> x = 1
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x >= 2 and (x/y) > 2 and y != 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
在第一个逻辑表达式中,x >= 2
为 False
,因此求值在 and
处停止。在第二个逻辑表达式中,x >= 2
为 True
,但 y != 0
为 False
,所以我们永远不会到达 (x/y)
。
在第三个逻辑表达式中,y != 0
在 (x/y)
计算之后,所以表达式因错误而失败。
在第二个表达式中,我们说 y != 0
充当了一个守护,以确保我们仅在 y
非零时才执行 (x/y)
。
调试
当错误发生时,Python 显示的回溯信息包含大量信息,但可能会让人不知所措。最有用的部分通常是:
- 错误是什么类型,以及
- 它发生在哪里。
语法错误通常很容易找到,但也有一些陷阱。空白错误可能很棘手,因为空格和制表符是不可见的,我们习惯于忽略它们。
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent
在这个例子中,问题在于第二行缩进了一个空格。但是错误消息指向 y
,这是误导性的。通常,错误消息指示发现问题的位置,但实际错误可能在代码的更早部分,有时在前一行。
总的来说,错误消息告诉你问题在哪里被发现,但这通常不是问题发生的原因。
术语表
主体 (body)
复合语句中的语句序列。
布尔表达式 (boolean expression)
值为 True
或 False
的表达式。
分支 (branch)
条件语句中可供选择的语句序列之一。
链式条件 (chained conditional)
具有一系列可选分支的条件语句。
比较运算符 (comparison operator)
比较其操作数的运算符之一:==
、!=
、>
、<
、>=
和 <=
。
条件语句 (conditional statement)
根据某个条件控制执行流程的语句。
条件 (condition)
条件语句中决定执行哪个分支的布尔表达式。
复合语句 (compound statement)
由头部和主体组成的语句。头部以冒号 (:) 结尾。主体相对于头部缩进。
守护模式 (guardian pattern)
我们构造一个带有额外比较的逻辑表达式,以利用短路行为。
逻辑运算符 (logical operator)
组合布尔表达式的运算符之一:and
、or
和 not
。
嵌套条件 (nested conditional)
出现在另一个条件语句分支中的条件语句。
回溯 (traceback)
发生异常时打印的正在执行的函数列表。
短路 (short circuit)
当 Python 正在评估逻辑表达式的中途,并且因为 Python 知道表达式的最终值而无需评估表达式的其余部分时,停止评估。
练习
练习 1: 重写你的工资计算程序,为超过 40 小时的工作时间支付 1.5 倍的小时费率。
Enter Hours: 45
Enter Rate: 10
Pay: 475.0
练习 2: 使用 try
和 except
重写你的工资程序,以便你的程序能够优雅地处理非数字输入,通过打印一条消息并退出程序。以下显示了程序的两次执行:
Enter Hours: 20
Enter Rate: nine
Error, please enter numeric input
Enter Hours: forty
Error, please enter numeric input
练习 3: 编写一个程序,提示用户输入一个介于 0.0 和 1.0 之间的分数。如果分数超出范围,则打印错误消息。如果分数在 0.0 和 1.0 之间,则使用下表打印一个等级:
Score Grade
>= 0.9 A
>= 0.8 B
>= 0.7 C
>= 0.6 D
< 0.6 F
Enter score: 0.95
A
Enter score: perfect
Bad score
Enter score: 10.0
Bad score
Enter score: 0.75
C
Enter score: 0.5
F
如上所示重复运行程序,以测试各种不同的输入值。
- 我们将在第 4 章学习函数,在第 5 章学习循环。 ↩︎
如果你在本书中发现错误,欢迎使用 Github 给我发送修正。