8. 复合语句

复合语句包含其他语句(组);它们以某种方式影响或控制那些其他语句的执行。通常,复合语句跨越多行,虽然一条完整的复合语句可以用简洁的形式包含在一行之中。

ifwhilefor语句实现传统的控制流结构。try为一组语句指定异常处理程序和/或清除代码,而with语句允许在一个代码块周围执行初始化和结束代码。函数和类定义在语法上同样也是复合语句。

复合语句由一个或多个“子句”组成。子句由header和suite组成。特定复合语句的子句header都在相同的缩进级别。每一个子句的header以一个唯一的标识关键字开始并以冒号结束。Suite是由一条子句控制的一组语句。一个suite可以是语句首冒号之后的同一行上紧跟一个或多个分号分隔的简单语句,也可以是后续行上一个或多个缩进的语句。只有后一种形式的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
                   | async_with_stmt
                   | async_for_stmt
                   | async_funcdef
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

请注意,语句总是以NEWLINE结束,后面可能有DEDENT还要注意,可选的续行子句永远以一个不能作为语句开始的关键字开始,因此不会有歧义(‘悬挂的else’问题在Python中通过要求嵌套的if语句必须缩进得到解决)。

为了清晰起见,下面小节中的语法规则的格式会将子句放在单独的一行。

8.1. if语句

if语句用于条件执行:

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

它通过对表达式逐个求值直到其中一个为真的方式准确地选择一个语句组(真和假的定义参见布尔操作 一节);然后执行这个suite(if语句的其它部分不会被执行或求值)。如果所有表达式都为假,则执行else子句的suite(如果存在)。

8.2. while语句

while语句用于重复执行,只要表达式为true:

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

它重复测试表达式,如果为真,则执行第一个suite;如果表达式为假(可能是第一次测试),则执行else子句的suite(如果存在)并终止循环。

在第一个suite中执行的break语句会终止循环,而不执行else子句的suite。在第一个suite中执行的continue语句跳过这个suite的其余部分,并返回测试表达式。

8.3. for语句

for语句用于遍历序列的元素(如字符串,元组或列表)或其他可迭代对象:

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

对expression_list进行一次计算;它应当产生一个可迭代的对象。expression_list的结果创建一个迭代器。然后,按照迭代器返回的顺序,对迭代器提供的每个项执行一次suite。使用赋值的标准规则依次将每个项赋值给目标列表(参见赋值语句),然后执行suite。当项用尽(当序列为空或迭代器引发StopIteration异常时,立即生效),执行else子句中的suite(如果存在)并且循环终止。

在第一个suite中执行的break语句会终止循环,而不执行else子句的suite。在第一个suite中执行的continue语句跳过这个suite的其余部分,并继续下一个项目,如果没有下一个项目,则继续使用else子句。

for循环对目标列表中的变量进行赋值。这将覆盖对这些变量的所有先前赋值,包括在for-loop的suite中做的那些:

for i in range(10):
    print(i)
    i = 5             # this will not affect the for-loop
                      # because i will be overwritten with the next
                      # index in the range

当循环完成时,目标列表中的名称不会被删除,但是如果序列为空,它们根本不会被循环赋值。提示:内建的range()函数返回整数的迭代器,它适用于模拟Pascal语言中for i := a to b do效果;例如,list(range(3))返回列表[0, 1, 2]

注意

当序列被循环修改时,会发生微妙的事情(只有可变类型的序列会发生,例如列表)。有一个内部计数器用于跟踪下一轮循环使用哪一个元素,并且每次迭代中会增加。 当计数器达到序列的长度时循环终止。这意味着如果语句组从序列中删除当前(或者前面的)元素,下一个元素将被跳过(因为它获取当前已经被处理过的元素的索引)。同样地,如果语句组在序列中当前元素之前插入一个元素,那么当前元素将在下一次循环被再次处理。这可能导致难以觉察的错误,但可以通过使用整个序列的切片生成临时拷贝避免这个问题,例如,

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

8.4. try语句

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

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

except子句指定一个或多个异常处理程序。try子句中没有发生异常时,不会执行异常处理程序。trysuite中发生异常时,将开始搜索异常处理程序。搜索依次检查异常子句直到找到与异常匹配的一个。一个无表达式的except子句,如果存在,必须是最后一个;它匹配任何异常。对于一个带有表达式的异常子句,该表达式将被求值,如果结果对象与异常“兼容”,则认为子句与异常匹配。如果对象是异常对象的类或基类,或者包含与异常兼容的项的元组,则对象与异常兼容。

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

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

当找到一个匹配的except子句时,异常就被赋给excep子句中as关键字后面的目标,然后执行excep子句的suite。所有的异常子句必须具有一个可执行的代码块。当到达该代码块的结尾时,在真个try语句之后执行正常继续。(这意味着如果同一个异常存在两个嵌套的处理器,且异常发生在内部处理器的try子句中,那么外边的处理器不会处理这个异常。)

当使用as target赋值异常时,将在except子句的末尾清除异常。这就好像

except E as N:
    foo

翻译成

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

这意味着必须将异常分配给不同的名称,以便能够在except子句后引用它。异常被清除,因为附加给它们的回溯使它们与栈帧形成一个引用循环,这将保持该帧中的所有局部变量一直存活,直到下一次垃圾收集发生。

在执行except子句suite之前,有关异常的详细信息存储在sys模块中,并且可以通过sys.exc_info()访问。sys.exc_info()返回一个由异常类,异常实例和一个traceback对象组成的三元组(见标准类型层次),这个三元组指示发生异常的程序位置。在从处理异常的函数返回时,sys.exc_info()值将恢复为之前的值(在调用之前)。

当控制流离开try子句的结尾时,将执行可选的else子句。[2]else子句中的异常不会被前面的except子句处理。

如果finally存在,它指定一个“清除”处理程序。首先执行try子句被执行,然后包括任何exceptelse子句。如果异常发生在任何子句中且没有被处理,那么异常会被临时保存起来。最后执行finally子句。如果有保存的异常,它会在finally子句结束时被重新抛出。如果finally子句抛出另外一个异常,那么保存的异常会被设置为新异常的上下文。如果finally子句执行returnbreak语句,则将丢弃保存的异常:

>>> def f():
...     try:
...         1/0
...     finally:
...         return 42
...
>>> f()
42

在执行finally子句期间,程序不能使用该异常信息。

returnbreakcontinue语句在try...finally语句的try suite中被执行,finally子句在‘出口’处同样被执行continue语句出现在finally子句中是非法的。(原因是当前实现的问题 — 该限制在未来可能会去掉)。

函数的返回值由执行的最后一个return语句决定。由于finally子句总是执行,在finally子句中执行的return语句将始终是最后一个执行的语句:

>>> def foo():
...     try:
...         return 'try'
...     finally:
...         return 'finally'
...
>>> foo()
'finally'

有关异常的其他信息,请参见异常部分,有关使用raise语句生成异常的信息,请参见raise语句

8.5. with语句

with语句用于使用由上下文管理器定义的方法包装代码块的执行(参见With语句上下文管理器一节)。这允许把常见的try...except...finally的用法模式封装起来以方便地重用。

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

具有一个“with_item”的with语句的执行如下进行:

  1. 计算上下文表达式(在with_item中给出的表达式)以获取上下文管理器。

  2. 加载上下文管理器的__exit__()供以后使用。

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

  4. 如果target包含在with语句中,则会为其分配__enter__()的返回值。

    注意

    with语句保证,如果__enter__()方法返回时没有错误,则将始终调用__exit__()因此,如果在给目标列表赋值过程中出现错误,它将被与语句组中发生的错误同样对待。参见下面的第6步。

  5. 执行语句组。

  6. 调用上下文管理器的__exit__()方法。如果异常导致suite退出,其类型,值和跟踪将作为参数传递到__exit__()否则,提供三个None参数。

    如果语句组由于异常退出,且__exit__()方法的返回值为假,异常将被重新引发。如果返回值为真,异常将被取消,并继续执行with语句之后的语句。

    如果语句组由于异常以外的其它任何原因退出,__exit__()的返回值将被忽略,执行将在退出发生的正常位置继续。

如果有多个条目,上下文管理器的处理如同嵌套的多个with语句:

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

等同于

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

在版本3.1中更改:支持多个上下文表达式。

请参阅

PEP 343 - “with”语句
Python with语句的规范,背景和示例。

8.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]

函数定义可以由一个或多个装饰器表达式包装。修饰符表达式在函数定义时于包含函数定义的定义域中求值。求值的结果必须是一个可调用对象,它以该函数对象为唯一的参数。调用的返回值绑定在函数名而不是函数对象上。多个修饰符是以嵌套的方式作用的。例如,下面的代码

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

大致相当于

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

除了原始函数不临时绑定到名称func

当一个或多个参数具有parameter = expression的形式时,称该函数具有“默认参数值。” 对于具有默认值的参数,对应的参数在调用时可以省略,在这种情况下使用参数的默认值。如果一个参数具有默认值,所有随后的参数直到“*” 也必须具有默认值 —— 这个限制在语法中没有表达出来的。

执行函数定义时,从左到右计算默认参数值。这意味着只在函数定义的时候该表达式求一次值,以后每次调用使用相同的“提前计算好的”值。这对于理解默认参数是可变对象时特别重要,例如列表或字典:如果函数修改了该对象(例如,向列表添加一个元素),默认值将受影响被修改。这通常不是想要的。有一种方法是使用None作为默认值,并在函数体中明确测试它,例如

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

函数调用语义在调用部分中有更详细的描述。函数调用永远会给参数列表中的所有的参数赋值,无论是位置参数还是关键字参数或默认值。如果出现“*identifier”的形式,那么它被初始化为一个可以接收任意多余位置参数元组,默认为空元组。如果有“**identifier”的形式,那么它被初识化为一个可以接收任意的多余关键字参数的新的字典,默认值为空字典。*”或“*identifier”之后的参数是keyword-only参数,只能使用关键字参数传递。

参数可以在参数名称后面带有“: expression”形式的注解。任何参数都可以具有注解,即使是*identifier**identifier形式。函数可以在参数列表的后面带有“-> expression” 形式的“返回值”注解。这些注解可以是任何有效的Python表达式,并且在执行函数定义时计算。注解可能以不同于它们在源代码中出现的顺序计算。注解的存在不改变函数的语义。注解的值可以通过函数对象的__annotations__字典属性访问,以参数的名称作为键。

也可以创建匿名函数(没有绑定到某个名称的函数),以在表达式中直接使用。这使用lambda表达式,在Lambdas中描述。注意,lambda表达式只是简化函数定义的简写;在“def”语句中定义的函数可以传递或分配给另一个名称,就像由lambda表达式定义的函数一样。def”形式实际上更强大,因为它允许执行多个语句和注解。

程序员注:函数是一级对象。在函数定义中执行的“def”语句定义了可以返回或传递的局部函数。在嵌套的函数中使用的自由变量可以访问包含该def的函数的局部变量。有关详细信息,请参见命名和绑定一节。

请参阅

PEP 3107 - 函数注释
函数注解的原始规范。

8.7. 类定义

类定义定义了一个类对象(参见标准类型层次结构):

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

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

class Foo:
    pass

等同于

class Foo(object):
    pass

然后,使用新创建的局部命名空间和原始的全局命名空间,在新的执行帧中执行类的suite(参见命名和绑定)。(通常,suite主要包含函数定义。)当类的语句组结束执行,它的执行帧被丢弃但是局部命名空间被保存下来。[4]最后使用inheritance序列作为基类创建一个类对象,并保存局部命名空间作为属性字典。类的名称被绑定到初识局部命名空间中类对象。

类创建可以使用元类进行深度定制。

类也可以装饰:就像装饰函数一样:

@f1(arg)
@f2
class Foo: pass

大致相当于

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

装饰器表达式的计算规则与函数装饰器的计算规则相同。然后将结果绑定到类名。

程序员注:在类定义中定义的变量是类属性;它们由实例共享。实例属性可以在一个方法中用self.name = value设置。类和实例属性都可以通过符号self.name访问,并且当以这种方式访问,实例属性隐藏具有相同名称的类属性。类属性可以用作实例属性的默认值,但使用可变值可能会导致意外的结果。描述器可用于创建具有不同实现细节的实例变量。

请参阅

PEP 3115 - Python 3中的元类 PEP 3129 - 类装饰器

8.8. 协程

版本3.5中的新功能。

8.8.1. 协程函数定义

async_funcdef ::=  [decorators] "async" "def" funcname "(" [parameter_list] ")" ["->" expression] ":" suite

Python协程的执行可以在多个点挂起和恢复(参见协程)。在协程的函数体中,任何awaitasync标识符成为保留关键字; await表达式async forasync with只能在协程函数体使用。

async def语法定义的函数总是协程函数,即使它们不包含await async关键字。

async def协程中使用yield表达式是SyntaxError

协程函数的示例:

async def func(param1, param2):
    do_stuff()
    await some_coroutine()

8.8.2. async for语句

async_for_stmt ::=  "async" for_stmt

异步迭代器能够在其iter实现中调用异步代码,并且异步迭代器可以在其next方法中调用异步代码。

async for语句允许在异步迭代器上方便的迭代。

下面的代码:

async for TARGET in ITER:
    BLOCK
else:
    BLOCK2

语义上等同于:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        BLOCK
else:
    BLOCK2

有关详细信息,请参见__aiter__()__anext__()

async def函数之外使用async forSyntaxError

8.8.3. async with语句

async_with_stmt ::=  "async" with_stmt

异步上下文管理器是能够在其enterexit方法中暂停执行的上下文管理器

下面的代码:

async with EXPR as VAR:
    BLOCK

语义上等同于:

mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__(mgr)
exc = True

VAR = await aenter
try:
    BLOCK
except:
    if not await aexit(mgr, *sys.exc_info()):
        raise
else:
    await aexit(mgr, None, None, None)

有关详细信息,请参见__aenter__()__aexit__()

async def函数之外使用async withSyntaxError

请参阅

PEP 492 - 具有async和await语法的协程

脚注

[1]异常传播到调用栈,除非有finally子句恰好引发另一个异常。这个新的异常导致旧的异常丢失。
[2]目前,除非出现异常或执行returncontinuebreak语句的情况,控制“流至结束”。
[3]作为函数体第一条语句出现的字符串字面值被转换成函数的__doc__属性,即函数的文档字符串
[4]作为类体的第一条语句出现的语句被转换为该命名空间的__doc__属性,即类的文档字符串