Python 3 文档(简体中文) 3.2.2 documentation

Version: 3.2.2

7. 复合语句

复合语句包含有其它语句 (组) . 它们以某种方式影响或控制其它语句的执行. 一般地, 复合语句会跨越多行, 但一个完整的复合语句也可以简化在一行中.

if, whilefor 语句实现了传统的控制结构. try 语句为一组语句指定了异常处理器和/或清理 (cleanup) 代码. with 语句允许为其内的代码提供初始化的清理 (finalization) 代码. 函数定义和类定义在语法上也被看作复合语句.

复合语句由一个或多个” 子句” (clause) 组成. 一个子句由一个头和一个 “语句序列”(suite) 组成. 一个具体复合语句内的所有子句头都具有相同的缩进层次. 每个子句 “头” 以一个唯一性标识关键字开始并以一个冒号结束. “语句序列”, 是该子句所控制的一组语句, 一个语句序列可以是与子句头处于同一行的, 在子句头冒号之后以分号分隔的多条简单语句, 或者也可以是在后面连续行中缩进的语句. 只有第二种情况下,子句序列才允许包括嵌套复合语句. 下面这样是非法的, 这样处理大部分原因是不易判断 else 子句与前面的 if 子句的配对关系:

if test1: if test2: print(x)

也要注意在这样的上下文中分号的优先级比冒号高, 所以在下面的例子中, 要么执行全部的 print() 调用, 要么一个也不执行:

if x < y < z: print(x); print(y); print(z)

总结:

compound_stmt ::=  if_stmt
                   | while_stmt
                   | for_stmt
                   | try_stmt
                   | with_stmt
                   | funcdef
                   | classdef
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

注意语句结尾的 NEWLINE 之后可能还有一个 DEDENT, 注意可选的续行子句都是以不能开始另一个语句的关键字开头的, 因此这里不存在歧义 (“悬挂 else 问题”已经因为 Python 要求缩进嵌套语句而解决掉了).

为了叙述清楚, 以下章节中每个子句的语法规则格式都会分行列出.

7.1. if 语句

if 语句用于条件执行:

if_stmt ::=  "if" expression ":" suite
             ( "elif" expression ":" suite )*
             ["else" ":" suite]

它对表达式逐个求值, 直到其中一个为真时, 准确地选择相应的一个语句序列 (对于真和假的定义参见 Boolean operations 节), 然后该执行语句序列 (if 语句的其它部分不会被执行和计算). 如果所有表达式都为假, 并且给出了 else 子句, 那么将执行它包括的语句序列.

7.2. while 语句

while 用于重复执行, 前提是条件表达式为真:

while_stmt ::=  "while" expression ":" suite
                ["else" ":" suite]

while 会重复地计算表达式的值, 并且如果为真, 就执行第一个语句序列; 如果为假 (可能在第一次比较时), 就执行else子句 (如果给出) 并退出循环.

在第一个语句序列中执行 break 语句就可以做到不执行 else 子句而退出循环. 在第一个语句序列执行 continue 语句可以跳过该子句的其余部分直接进入下次的表达式测试.

7.3. for 语句

for 语句用于迭代有序类型 (像串, 元组或列表) 或其它可迭代对象的元素:

for_stmt ::=  "for" target_list "in" expression_list ":" suite
              ["else" ":" suite]

只计算一次 expression_list , 它应该生成一个迭代器对象. 然后在迭代器每次提供一个元素时就会执行语句序列 (suite) 一次, 元素按索引升序循环给出. 每个元素使用标准的赋值规则 (见 Assignment statements) 依次赋给循环的 target_list, 然后执行语句序列. 当迭代完毕后(当有序类型对象为空, 或者迭代器抛出异常 StopIteration 时立即结束循环), 就执行 else 子句 (如果给出) 中的语句序列, 最后结束循环.

在第一个语句序列中执行 break 语句可以不执行 else 子句就退出循环. 在第一个语句序列中执行 continue 语句可以跳过该子句的其余部分, 直接处理下个元素, 或者如果没有下个元素了, 就进入 else 子句.

语句序列可以对 target_list 中的变量赋值, 这不影响 for 语句赋下一项元素给它.

在循环结束后, 这个 target_list 并不会删除, 但如果有序类型对象为空, 它根本就不会在循环中赋值. 小技巧:内置函数 range() 返回一个整数列表, 可以用于模拟Pascal语言中的 for i := a to b 的行为, 例如 list(range(3)) 返回列表 [0, 1, 2].

Note

如果在循环中要修改有序类型对象 (仅对可变类型而言, 即列表), 这里有一些要注意的地方. 有一个内部计数器用于跟踪下一轮循环使用哪一个元素, 并且每次迭代就增加一次. 当这个计数器到达有序类型对象的长度时该循环就结束了. 这意味着如果语句序列删除了当前元素 (或一个之前的元素) 时, 下一个元素就会被跳过去 (因为当前索引值的元素已经处理过了). 类似地, 如果在当前元素前插入了一个元素, 则当前元素会在下一轮循环再次得到处理. 这可能会导致难以觉察的错误, 但可以通过使用含有整个有序类型对象的片断而生成的临时拷贝避免这个问题, 例如:

for x in a[:]:
    if x < 0: a.remove(x)

7.4. try 语句

try 语句为一组语句指定异常处理器和/或清理代码:

try_stmt  ::=  try1_stmt | try2_stmt
try1_stmt ::=  "try" ":" suite
               ("except" [expression ["as" target]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               "finally" ":" suite

except 子句指定了一个或多个异常处理器. 当在 try 子句中没有异常发生时, 异常处理器将不被执行. 当在 try 子句中有异常发生时, 就会开始搜索异常处理器. 它会按书写顺序搜索每个子句, 直到有一个匹配的处理器找到为止. 如果存在一个没有指定异常的 except 子句, 它必须放在最后, 它会匹配任何异常. 当一个 except 子句携带了一个表达式时, 这个表达式会被求值, 如果结果与该异常” 兼容”, 那么该子句就匹配上了这个异常. 对象与异常兼容是指, 对象与这个异常的类或者基类相同, 或者对象是一个元组, 它的某个项包括与该异常兼容的对象.

如果没有 except 子句匹配异常, 异常处理器的搜索工作将继续在外层代码和调用栈上进行. [1]

如果在 except 子句头部计算表达式时引发了异常, 那么就会中断原异常处理器的搜索工作, 而在外层代码和调用栈上搜索新的异常处理器 (就好像是整个 try 语句发生了异常一样).

当找到了一个匹配的 except 子句时, 异常对象就被赋给 except 子句中关键字 as 指定的目标对象 (如果给出), 并且执行其后的语句序列. 每个 except 子句必须一个可执行代码块. 当执行到该代码块末尾时, 会跳转到整个 try 语句之后继续正常执行 (这意味着, 如果有两个嵌套的异常处理器要处理同一个异常的话, 那么如果异常已经在内层处理了, 外层处理器就不会响应这个异常了).

在使用 as target 形式将异常赋值时, 它会在 except 子句结束时自动清除掉:

except E as N:
    foo

被转换为:

except E as N:
    try:
        foo
    finally:
        del N

这意味着如果你想在 except 子句之后访问这个异常, 就必须在处理它时把它赋给另一个变量. 这么设计的原因在于回溯跟踪对象与这个异常关联, 而它们与栈桢会构成了一个引用循环, 从而使栈桢上所有局部变量直到下次垃圾回收时才被回收.

在某个 except 子句的语句序列被执行前, 异常的详细情况记录在 sys 模块中, 可以通过函数 sys.exc_info() 访问. sys.exc_info() 返回一个元组, 包括 exc_type, 异常类; exc_value, 异常实例; exc_traceback, 记录异常发生点的回溯对象 (见 标准类型层次 ). sys.exc_info() 值会在处理异常的函数返回时会恢复它们之前的值 (调用之前).

当控制从 try 子句的尾部结束时 (即没有异常发生时), 就执行可选的 else 子句. [2]else 子句中引发的异常不会在前面的 except 子句里得到处理.

如果给出了 finally, 它就指定一个”清理”处理器 (cleanup handler). 这种语法下, try 子句会得到执行, 也包括任何 exceptelse 子句. 如果在任何子句中发生了异常, 并且这个异常没有得到处理, 该异常就会被临时保存起来. 之后, finally 子句就会得以执行. 然后暂存的异常在 finally 子句末尾被重新引发. 如果执行 finally 子句时引发了另一个异常或执行了:keyword:returnbreak 语句, 就会抛弃保存的异常. 在执行 finally 子句时异常信息是无效的.

当在 try ...finally 语句中的 try 语句序列中执行 return , breakcontinue 时, finally 子句也会 “在退出的路上” 被执行. 在 finally 子句中的 continue 语句是非法的 (这缘于因为当前实现中的一个问题——以后可能会去掉这个限制).

关于异常的更多信息可以在 Exceptions 中找到, 关于如何使用 raise 语句产生异常的信息, 可以在 The raise statement 中找到.

7.5. with 语句

with 语句用于封装上下文管理器 (见 With 语句的上下文管理器) 定义的方法的代码块的执行. 这允许我们方便地复用常见的 try...except...finally 使用模式.

with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]

with 语句对一个 “item” 的执行按如下方式进行:

  1. 对上下文表达式求值得到一个上下文管理器.

  2. 调用上下文管理器的 __enter__() 方法.

  3. 如果 with 语句包括有 target, 就将 __enter__() 的返回值赋给它.

    Note

    with 语句保证了如果 __enter__() 是无错返回的, 就一定会调用 __exit__() 方法. 如果在给 target list 赋值时发生错误, 就按在语句序列里发生错误同样对待, 参见下面的步骤6.

  4. 执行语句序列.

  5. 调用上下文管理器的 __exit__() 方法. 如果语句序列导致了一个异常, 那么异常的异常的类型,值和回溯对象都作为参数传递给 __exit__() 方法. 否则, 使用 None 作为参数.

    如果语句序列因为异常退出, 且 __exit__() 方法返回假, 那么异常就会重新抛出. 如果返回值为真, 异常就会被 “吃掉”, 并且执行会在 with 语句之后继续.

    如果语句序列不是因为异常的原因退出的, 那么 __exit__() 的返回值会被忽略掉, 并且在退出点后继续运行程序.

使用多个项时, 上下文管理器就按多个嵌套 with 语句处理:

with A() as a, B() as b:
    suite

等价于:

with A() as a:
    with B() as b:
        suite

Changed in version 3.1:

Changed in version 3.1: Support for multiple context expressions.

See also

PEP 0343 - The “with” statement

Python with 语句的规范, 背景和例子.

7.6. 函数定义

“函数定义”定义了一个用户定义函数对象 (见 标准类型层次):

funcdef        ::=  [decorators] "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite
decorators     ::=  decorator+
decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
dotted_name    ::=  identifier ("." identifier)*
parameter_list ::=  (defparameter ",")*
                    (  "*" [parameter] ("," defparameter)*
                    [, "**" parameter]
                    | "**" parameter
                    | defparameter [","] )
parameter      ::=  identifier [":" expression]
defparameter   ::=  parameter ["=" expression]
funcname       ::=  identifier

函数定义是一个可执行语句. 执行它会在当前局部名字空间中将函数名字与函数对象 (一个函数可执行代码的包装对象) 绑定在一起. 这个函数对象包括一个全局名字空间的引用, 以便在调用时使用.

函数定义不执行函数体, 它们只在调用时执行. [3]

函数定义前可能有若干个 decorator 表达式. Decorator 表达式于函数定义时, 且在函数定义所在的作用域里求值. 结果必须是可调用的, 它以函数对象为唯一参数, 然后它的返回值将与函数名绑定, 而不是函数对象本身. 多个Decorator表达式可以嵌套使用, 例如, 以下代码:

@f1(arg)
@f2
def func(): pass

等价于:

def func(): pass
func = f1(arg)(f2(func))

当一个或多个参数以 parameter = expression 形式出现时, 我们就说这个函数具有” 默认参数值”. 对于有默认参数值的参数, 可以在调用时省略它们, 此时他们被赋予默认值. 如果某参数具有默认值, 则它之后直到 “*” 的所有参数都必须有默认值 — 这是以上语法说明中没有表达出来的一个限制.

默认参数值是在执行函数定义时计算的. 这意味着这个表达式仅仅求值一次, 时间是函数定义时, 并且所有调用都使用这个 “预计算” 的值. 在理解默认参数值是一个像列表, 字典这样的可变对象时, 这需要特别注意: 如果修改了这个对象 (例如给列表追加了一项), 默认值也随之修改. 这通常是应该避免的. 避免这个麻烦的一个方法就是使用 None 作默认值, 然后在函数体中作显式的测试, 例如:

def whats_on_the_telly(penguin=None):
    if penguin is None:
        penguin = []
    penguin.append("property of the zoo")
    return penguin

函数调用语义的详细说明, 参见 Calls 一节. 函数调用通常会给每个参数表中的参数赋一个值, 值的来源要么是位置参数, 要么是关键字参数或者是默认值. 如果给出了 “*identifier” 语法, 这个标识符就被初始化成一个接受所有额外位置参数的元组, 默认为空元组. 如果使用了 “**identifier” 语法, 它就被初始化成一个接受所有额外关键字参数的字典, 默认为一个新的空字典. 在 “*” or “*identifier” 之后的参数必须是纯关键字参数, 并且只能使用指定关键字的方式传递.

可以使用参数名之后 “: expression” 语法为参数添加一个注解. 任何参数都可以有注解,甚至包括 *identifier**identifier. 函数也可以有一个 “返回” 注解, 语法是在参数列表之后使用 “-> expression”. 这些注解可以是任何合法的Python表达式, 它是在函数定义时求值的, 但它们的求值顺序可能与在源代码中的书写顺序不同. 使用注解不会改变函数的语义, 注解的值可以通过函数对象的属性 __annotations__ 访问, 它是一个字典, 键是参数名字.

也可以创建匿名函数 (没有名字与之绑定的函数), 它可以直接在表达式中使用. 这是通过lambda表达式实现的, 详见 Lambdas. 注意lambda形式只是一个简单函数的简写形式, 以 def 定义的函数也可以被传递, 或者赋予另一个名字, 与以lambda定义的函数一样. 以 def 定义的函数功能要更强大些, 因为它允许执行多条语句和注解.

程序员注意: 在函数定义中执行的 def 可以创建一个局部函数, 可用于返回和传递. 在嵌套函数里, 可以通过自由变量访问包括这个函数定义的函数的局部变量. 详见 Naming and binding.

7.7. 类定义

“类定义”定义一个类对象 (参见 标准类型层次):

classdef    ::=  [decorators] "class" classname [inheritance] ":" suite
inheritance ::=  "(" [argument_list [","] ] ")"
classname   ::=  identifier

类定义是一条可执行语句. 继承列表通常给出一个基类列表 (更高级的用法参见 类创建的定制 ). 所以, 列表中的每个项都应该是允许子类化 (subclassing) 的类对象. 没有继承列表的类默认继承自基类 object; 因此:

class Foo:
    pass

等价于

class Foo(object):
    pass

类的语句序列在新的栈桢结构 (见 Naming and binding) 内执行, 它会使用一个新建的局部名字空间和现有全局名字空间 (这个语句序列里通常只有函数定义). 当这个语句序执行结束时, 就会丢弃掉这个栈桢结构, 但其局部名字空间被保存了下来. [#]_ 之后, 使用继承关系列表作为基类, 使用保存的名字空间作为属性字典, 创建新的类对象. 最后, 这个新类对象的名字, 会在最初的局部名字空间中与该类对象绑定.

类的创建可以使用 metaclasses 中介绍的技术进行大量定制.

类也可以使用decorate表达式; 类似于函数,:

@f1(arg)
@f2
class Foo: pass

等价于:

class Foo: pass
Foo = f1(arg)(f2(Foo))

程序员注意: 在类定义中定义的变量是类属性, 它们由所有类实例共享. 实例属性可以使用 self.name = value 设置值. 实例属性和类属性都可以使用这种方式访问, 但实例属性会掩盖掉类属性. 类属性可以用于实例属性的默认值, 但使用可变对象作为默认值可能导致并非预期的效果, 还可以使用 Descriptors 创建具有不同实现的实例属性.

See also

PEP 3116 - Metaclasses in Python 3 (Python 3中的元类) PEP 3129 - Class Decorators (类装饰器)

Footnotes

[1]只有在 finally 子句没有取消 (negate) 异常时, 异常才会传播出调用栈.
[2]目前, 尾部结束 (“flows off the end”) 不包括出现异常的情况, 以及执行 return, continuebreak 语句的情况.
[3]在函数体作为第一条语句出现的字符串字面值, 会被转换为函数的 __doc__ 属性, 即作为函数的 docstring.