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子句,如果存在,必须是最后一个;它匹配任何异常。对于带有表达式的except子句,该表达式为计算,并且如果生成的对象与异常“兼容”,则子句与异常匹配。如果对象是异常对象的类或基类,或者包含与异常兼容的项的元组,则对象与异常兼容。

如果没有except子句匹配异常,则在周围代码和调用堆栈中继续搜索异常处理程序。[1]

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

当找到一个匹配的except子句时,异常就被赋给excep子句中as关键字后面的目标,然后执行excep子句的suite。所有except子句必须有一个可执行块。当达到此块的结束时,在整个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]然后使用基类的继承列表和属性字典的保存的本地命名空间创建类对象。类名称绑定到原始本地命名空间中的此类对象。

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

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

@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__属性,即类的文档字符串