John Sturtz
if 语句简介
我们先从最基础的 if 语句开始。其最简单的形式如下所示:
if <expr>:
<statement>
在上述形式中:
<expr>是一个在布尔上下文中求值的表达式(如《Python 运算符与表达式》教程中“逻辑运算符”一节所述)。<statement>是一条有效的 Python 语句,必须缩进。(很快你就会明白原因。)
如果 <expr> 为真(即求值得到一个“真值”),则执行 <statement>;如果 <expr> 为假,则跳过 <statement>,不执行。
注意:<expr> 后面的冒号(:)是必需的。某些编程语言要求将 <expr> 括在括号中,但 Python 不需要。
以下是这种 if 语句的几个示例:
>>> x = 0
>>> y = 5
>>> if x < y: # 真值
... print('yes')
...
yes
>>> if y < x: # 假值
... print('yes')
...
>>> if x: # 假值
... print('yes')
...
>>> if y: # 真值
... print('yes')
...
yes
>>> if x or y: # 真值
... print('yes')
...
yes
>>> if x and y: # 假值
... print('yes')
...
>>> if 'aul' in 'grault': # 真值
... print('yes')
...
yes
>>> if 'quux' in ['foo', 'bar', 'baz']: # 假值
... print('yes')
...
注意:如果你在交互式 REPL 会话中尝试这些示例,输入完
print('yes')后按回车,你会发现什么也没发生。
因为这是一个多行语句,你需要再按一次回车,告诉解释器你已经输入完毕。在脚本文件中运行代码时则不需要这个额外的空行。
语句分组:缩进与代码块
目前为止一切顺利。
但假设你想先判断一个条件,如果为真就执行多个操作,例如:
如果天气好,我就:
- 修剪草坪
- 给花园除草
- 带狗散步
(如果天气不好,我就不做这些事。)
在上面的所有示例中,每个 if <expr>: 后面都只跟了一条 <statement>。我们需要一种方式来表示:“如果 <expr> 为真,就执行以下所有操作。”
大多数编程语言的做法是定义一种语法结构,将多条语句组合成一个复合语句(compound statement)或代码块(block)。代码块在语法上被视为一个整体。当它作为 if 语句的目标时,若 <expr> 为真,则执行块内所有语句;若为假,则全部跳过。
几乎所有编程语言都支持定义代码块,但实现方式各不相同。下面我们看看 Python 是如何做的。
Python:一切皆靠缩进
Python 遵循一种称为 off-side rule(越位规则)的约定——这一术语由英国计算机科学家 Peter J. Landin 提出(源自足球中的“越位”规则)。遵循 off-side rule 的语言通过缩进来定义代码块。Python 是少数采用此规则的语言之一。
回想一下《Python 程序结构》教程中提到的内容:缩进在 Python 程序中有特殊意义。现在你知道原因了:缩进用于定义复合语句或代码块。在 Python 中,缩进级别相同的连续语句被视为同一个代码块的一部分。
因此,Python 中的复合 if 语句看起来像这样:
if <expr>:
<statement>
<statement>
...
<statement>
<following_statement>
这里,所有缩进级别相同的语句(第 2 到第 5 行)被视为同一个代码块。如果 <expr> 为真,则执行整个代码块;如果为假,则跳过。无论哪种情况,之后都会继续执行缩进级别更低的 <following_statement>(第 6 行)。
注意:Python 文档中常将这种由缩进定义的语句组称为 suite(套件)。本教程系列中,“代码块”和“suite”可互换使用。
考虑以下脚本文件 foo.py:
if 'foo' in ['bar', 'baz', 'qux']:
print('Expression was true')
print('Executing statement in suite')
print('...')
print('Done.')
print('After conditional')
运行 foo.py 的输出如下:
C:\> python foo.py
After conditional
第 2 到第 5 行的四个 print() 语句缩进级别相同,构成一个代码块。但由于条件为假,这些语句全部被跳过。无论块内语句是否执行,程序都会继续执行缩进更少的下一行(第 6 行的 print())。
代码块可以任意深度嵌套。每次缩进定义一个新块,每次减少缩进结束前一个块。这种结构清晰、一致且直观。
下面是一个更复杂的脚本 blocks.py:
# Does line execute? Yes No
# --- --
if 'foo' in ['foo', 'bar', 'baz']: # x
print('Outer condition is true') # x
if 10 > 20: # x
print('Inner condition 1') # x
print('Between inner conditions') # x
if 10 < 20: # x
print('Inner condition 2') # x
print('End of outer condition') # x
print('After outer condition') # x
运行该脚本的输出如下:
C:\> python blocks.py
Outer condition is true
Between inner conditions
Inner condition 2
End of outer condition
After outer condition
提示:REPL 中输入多行语句时需要额外空行的原因,正是因为 off-side rule——解释器无法知道你是否已经输完了当前代码块的最后一行。
其他语言怎么做?
你可能好奇:不使用 off-side rule 的语言是如何定义代码块的?
大多数编程语言使用特殊符号标记代码块的开始和结束。例如,在 Perl 中,代码块用花括号 {} 包围:
# (这是 Perl,不是 Python)
if (<expr>) {
<statement>;
<statement>;
...
<statement>;
}
<following_statement>;
C/C++、Java 等大量语言也采用类似的花括号方式。
而 Algol 和 Pascal 等语言则使用 begin 和 end 关键字来包围代码块。
哪种方式更好?
“更好”见仁见智。程序员通常对自己习惯的方式有强烈偏好,关于 off-side rule 的争论常常很激烈。
优点:
- Python 的缩进方式简洁、清晰且一致。
- 在不使用 off-side rule 的语言中,代码缩进与逻辑结构完全无关。你可能写出缩进看起来正确、但实际执行逻辑不同的代码,造成误解。而在 Python 中,这种错误几乎不可能发生。
- 强制使用缩进定义代码块,迫使你遵守本应遵循的代码格式规范。
缺点:
- 许多程序员不喜欢被强制使用某种方式。他们对代码外观有强烈个人偏好,不喜欢被限制。
- 某些编辑器会在缩进行前混合插入空格和制表符(tab),导致 Python 解释器难以判断缩进层级。不过,通常可以配置编辑器避免此行为。而且,无论使用何种语言,源代码中混用空格和 tab 通常都不被推荐。
不管你喜不喜欢,只要用 Python 编程,就必须接受 off-side rule。Python 中所有控制结构都使用它(后续教程会进一步说明)。
值得一提的是,许多原本习惯传统代码块定义方式的程序员,起初对 Python 的做法感到不适,但后来逐渐适应,甚至开始偏爱这种方式。
else 和 elif 子句
现在你已经知道如何使用 if 语句有条件地执行单条语句或一个代码块。接下来,我们看看还能做什么。
有时,你希望在条件为真时走一条路径,为假时走另一条路径。这可以通过 else 子句实现:
if <expr>:
<statement(s)>
else:
<statement(s)>
如果 <expr> 为真,执行第一个代码块,跳过第二个;如果为假,则跳过第一个,执行第二个。无论哪种情况,之后都会继续执行 else 块之后的代码。两个代码块都通过缩进定义。
在下面的例子中,x 小于 50,因此执行第一个代码块(第 4–5 行),跳过第二个(第 7–8 行):
>>> x = 20
>>> if x < 50:
... print('(first suite)')
... print('x is small')
... else:
... print('(second suite)')
... print('x is large')
...
(first suite)
x is small
而这里 x 大于 50,因此跳过第一个代码块,执行第二个:
>>> x = 120
>>> if x < 50:
... print('(first suite)')
... print('x is small')
... else:
... print('(second suite)')
... print('x is large')
...
(second suite)
x is large
你还可以基于多个条件进行分支。这时可以使用一个或多个 elif(即 “else if” 的缩写)子句。Python 会依次求值每个 <expr>,并执行第一个为真的条件对应的代码块。如果所有条件都为假,且存在 else 子句,则执行 else 块:
if <expr>:
<statement(s)>
elif <expr>:
<statement(s)>
elif <expr>:
<statement(s)>
...
else:
<statement(s)>
你可以指定任意数量的 elif 子句。else 子句是可选的;如果存在,只能有一个,且必须放在最后:
>>> name = 'Joe'
>>> if name == 'Fred':
... print('Hello Fred')
... elif name == 'Xander':
... print('Hello Xander')
... elif name == 'Joe':
... print('Hello Joe')
... elif name == 'Arnold':
... print('Hello Arnold')
... else:
... print("I don't know who you are!")
...
Hello Joe
最多只会执行其中一个代码块。如果没有 else 子句,且所有条件都为假,则不执行任何块。
提示:冗长的
if/elif/else结构有时显得不够优雅,尤其是当每个分支只是简单语句(如print())时。很多时候,有更“Pythonic”的替代方案。
例如,上面的例子可以用dict.get()方法改写:
>>> names = {
... 'Fred': 'Hello Fred',
... 'Xander': 'Hello Xander',
... 'Joe': 'Hello Joe',
... 'Arnold': 'Hello Arnold'
... }
>>> print(names.get('Joe', "I don't know who you are!"))
Hello Joe
>>> print(names.get('Rick', "I don't know who you are!"))
I don't know who you are!
回想《Python 字典》教程:dict.get(key, default) 方法在字典中查找 key,若存在则返回对应值,否则返回 default。
if 语句中的 elif 子句也采用短路求值(short-circuit evaluation),类似于 and 和 or 运算符。一旦某个条件为真并执行了对应代码块,剩余条件将不再求值。如下例所示:
>>> var # 未定义
Traceback (most recent call last):
File "<pyshell#58>", line 1, in <module>
var
NameError: name 'var' is not defined
>>> if 'a' in 'bar':
... print('foo')
... elif 1/0:
... print("This won't happen")
... elif var:
... print("This won't either")
...
foo
第二个表达式包含除零错误,第三个引用了未定义变量 var,两者都会引发异常。但由于第一个条件已为真,后两个根本不会被求值。
单行 if 语句
通常我们会这样写 if 语句:
if <expr>:
<statement>
但 Python 也允许将整个 if 语句写在一行:
if <expr>: <statement>
甚至可以在同一行写多条语句,用分号分隔:
if <expr>: <statement_1>; <statement_2>; ...; <statement_n>
但这意味着什么?有两种可能的理解:
- 如果
<expr>为真,执行<statement_1>;然后无条件执行<statement_2>到<statement_n>。 - 如果
<expr>为真,执行所有语句;否则,都不执行。
Python 采用第二种解释。因为分号的优先级高于冒号(在计算机术语中,分号“绑定更紧密”),所以这些语句被视为一个代码块,要么全部执行,要么全部跳过:
>>> if 'f' in 'foo': print('1'); print('2'); print('3')
...
1
2
3
>>> if 'z' in 'foo': print('1'); print('2'); print('3')
...
elif 或 else 子句也可以在同一行写多条语句:
>>> x = 2
>>> if x == 1: print('foo'); print('bar'); print('baz')
... elif x == 2: print('qux'); print('quux')
... else: print('corge'); print('grault')
...
qux
quux
>>> x = 3
>>> if x == 1: print('foo'); print('bar'); print('baz')
... elif x == 2: print('qux'); print('quux')
... else: print('corge'); print('grault')
...
corge
grault
虽然解释器允许这样做,但通常不推荐,因为它会降低可读性,尤其对于复杂的 if 语句。PEP 8 明确反对这种写法。
当然,这在一定程度上是个人偏好。大多数人会觉得下面的写法更清晰、更易理解:
>>> x = 3
>>> if x == 1:
... print('foo')
... print('bar')
... print('baz')
... elif x == 2:
... print('qux')
... print('quux')
... else:
... print('corge')
... print('grault')
...
corge
grault
不过,如果 if 语句足够简单,写在一行也无可厚非。比如下面这种写法通常不会引起争议:
debugging = True # 设为 True 以开启调试
# ...
if debugging: print('About to call function foo()')
foo()
条件表达式(Python 的三元运算符)
Python 还支持一种额外的决策结构,称为条件表达式(conditional expression),在 Python 文档中也被称为条件运算符或三元运算符。它在 PEP 308 中提出,并于 2005 年由 Guido 批准加入语言。
其最简形式如下:
<expr1> if <conditional_expr> else <expr2>
这与前面介绍的 if 语句不同:它不是控制结构,而是一个表达式。首先求值 <conditional_expr>,如果为真,整个表达式返回 <expr1>;如果为假,则返回 <expr2>。
注意其非直观的顺序:中间的条件先求值,再根据结果返回两端之一。以下示例有助于理解:
>>> raining = False
>>> print("Let's go to the", 'beach' if not raining else 'library')
Let's go to the beach
>>> raining = True
>>> print("Let's go to the", 'beach' if not raining else 'library')
Let's go to the library
>>> age = 12
>>> s = 'minor' if age < 21 else 'adult'
>>> s
'minor'
>>> 'yes' if ('qux' in ['foo', 'bar', 'baz']) else 'no'
'no'
提示:Python 的条件表达式类似于其他语言(如 C、Perl、Java)中的
<conditional_expr> ? <expr1> : <expr2>语法。那些语言中?:运算符常被称为“三元运算符”,这也是 Python 条件表达式有时被称为“Python 三元运算符”的原因。
实际上,PEP 308 中曾考虑过采用? :语法,但最终选择了现在的形式。
条件表达式的常见用途是变量赋值选择。例如,假设你想找出两个数中较大的那个。当然,Python 内置了 max() 函数,但假设你要自己实现。
使用标准 if/else 语句:
>>> if a > b:
... m = a
... else:
... m = b
...
而使用条件表达式更短,也更易读:
>>> m = a if a > b else b
记住:条件表达式在语法上是一个表达式,可以作为更大表达式的一部分。它的运算符优先级非常低,因此通常需要用括号将其单独分组。
在下面的例子中,+ 的优先级高于条件表达式,因此先计算 1 + x 和 y + 2,再进行条件判断。第二行的括号是多余的:
>>> x = y = 40
>>> z = 1 + x if x > y else y + 2
>>> z
42
>>> z = (1 + x) if x > y else (y + 2)
>>> z
42
如果你想先求值条件表达式,就需要用括号包围它。下面例子中,(x if x > y else y) 先求值,结果是 y(40),所以 z = 1 + 40 + 2 = 43:
>>> x = y = 40
>>> z = 1 + (x if x > y else y) + 2
>>> z
43
建议:在复杂表达式中使用条件表达式时,即使括号不是必需的,也最好加上以提高可读性。
条件表达式也支持短路求值:
- 如果
<conditional_expr>为真,返回<expr1>,不求值<expr2>; - 如果为假,返回
<expr2>,不求值<expr1>。
可通过会引发错误的表达式验证:
>>> 'foo' if True else 1/0
'foo'
>>> 1/0 if False else 'bar'
'bar'
两种情况下 1/0 都未被求值,因此没有异常。
条件表达式还可以链式连接,作为 if/elif/else 的替代:
>>> s = ('foo' if (x == 1) else
... 'bar' if (x == 2) else
... 'baz' if (x == 3) else
... 'qux' if (x == 4) else
... 'quux'
... )
>>> s
'baz'
虽然语法正确,但相比 if/elif/else 语句并无明显优势。
Python 的 pass 语句
有时,你可能想写一个代码桩(code stub)——即一个占位符,用于将来实现某段尚未编写的代码。
在使用符号界定代码块的语言(如 Perl 和 C 的花括号)中,可以用空界定符定义代码桩。例如,以下 Perl 或 C 代码是合法的:
// 这不是 Python
if (x)
{
}
这里,空花括号定义了一个空块。即使 x 为真,程序也会安静地什么都不做。
但 Python 使用缩进而非符号,因此无法指定空块。如果你写了 if <expr>:,后面必须跟一些内容——要么在同一行,要么在下一行缩进。
考虑以下脚本 foo.py:
if True:
print('foo')
运行它会报错:
C:\> python foo.py
File "foo.py", line 3
print('foo')
^
IndentationError: expected an indented block
Python 的 pass 语句解决了这个问题。它不改变程序行为,仅作为一个占位符,在语法上需要语句但又不想执行任何操作时使用:
if True:
pass
print('foo')
现在 foo.py 可以正常运行:
C:\> python foo.py
foo
总结
通过本教程,你已经开始编写超越简单顺序执行的 Python 代码:
- 你接触了控制结构的概念——这类复合语句可以改变程序的控制流(即语句的执行顺序)。
- 你学会了如何将多条语句组合成一个代码块(或 suite)。
- 你遇到了第一个控制结构——
if语句,它可以根据程序数据的求值结果,有条件地执行语句或代码块。
这些概念对开发更复杂的 Python 程序至关重要。
接下来的两篇教程将介绍两种新的控制结构:while 语句和 for 语句。它们支持迭代——即重复执行一条语句或一个代码块。