Skip to main content

条件执行

布尔表达式

布尔表达式 (boolean expression) 是值为真 (true) 或假 (false) 的表达式。以下示例使用运算符 ==,它比较两个操作数,如果它们相等则生成 True,否则生成 False

>>> 5 == 5
True
>>> 5 == 6
False

TrueFalse 是属于 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):andornot。这些运算符的语义(含义)与其在英语中的含义相似。例如,

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 逻辑流程图

如果逻辑条件为真,则执行缩进的语句。如果逻辑条件为假,则跳过缩进的语句。

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 逻辑流程图 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 逻辑流程图 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 语句流程图 嵌套 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 捕获异常

之前我们看到了一个代码段,其中我们使用了 inputint 函数来读取和解析用户输入的整数。我们也看到了这样做是多么危险:

>>> 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”。tryexcept 的目的是,你知道某段指令序列可能会出问题,并且你想添加一些语句以便在发生错误时执行。如果没有错误,这些额外的语句(except 块)将被忽略。

你可以将 Python 中的 tryexcept 功能视为对一系列语句的“保险策略”。

我们可以像下面这样重写我们的温度转换器:

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 >= 2False,因此无论 (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 >= 2False,因此求值在 and 处停止。在第二个逻辑表达式中,x >= 2True,但 y != 0False,所以我们永远不会到达 (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) 值为 TrueFalse 的表达式。 分支 (branch) 条件语句中可供选择的语句序列之一。 链式条件 (chained conditional) 具有一系列可选分支的条件语句。 比较运算符 (comparison operator) 比较其操作数的运算符之一:==!=><>=<=。 条件语句 (conditional statement) 根据某个条件控制执行流程的语句。 条件 (condition) 条件语句中决定执行哪个分支的布尔表达式。 复合语句 (compound statement) 由头部和主体组成的语句。头部以冒号 (:) 结尾。主体相对于头部缩进。 守护模式 (guardian pattern) 我们构造一个带有额外比较的逻辑表达式,以利用短路行为。 逻辑运算符 (logical operator) 组合布尔表达式的运算符之一:andornot。 嵌套条件 (nested conditional) 出现在另一个条件语句分支中的条件语句。 回溯 (traceback) 发生异常时打印的正在执行的函数列表。 短路 (short circuit) 当 Python 正在评估逻辑表达式的中途,并且因为 Python 知道表达式的最终值而无需评估表达式的其余部分时,停止评估。

练习

练习 1: 重写你的工资计算程序,为超过 40 小时的工作时间支付 1.5 倍的小时费率。

Enter Hours: 45
Enter Rate: 10
Pay: 475.0

练习 2: 使用 tryexcept 重写你的工资程序,以便你的程序能够优雅地处理非数字输入,通过打印一条消息并退出程序。以下显示了程序的两次执行:

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

如上所示重复运行程序,以测试各种不同的输入值。


  1. 我们将在第 4 章学习函数,在第 5 章学习循环。 ↩︎

如果你在本书中发现错误,欢迎使用 Github 给我发送修正。