Skip to main content

8. 复合语句

复合语句包含(组)其他语句;它们以某种方式影响或控制那些其他语句的执行。一般来说,复合语句跨多行,虽然在简单形式中,整个复合语句可能包含在一行中。

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

复合语句由一个或多个“子句”组成。一个子句包含一个标题和一个’suite’。特定复合语句的子句头都在相同的缩进级别。每个子句标题以唯一标识关键字开头,并以冒号结尾。套件是由一个子句控制的一组语句。套件可以是在标题的同一行上的一个或多个分号分隔的简单语句,在标题的冒号之后,或者它可以是在后续行上的一个或多个缩进语句。只有套件的后一种形式可以包含嵌套复合语句;以下是非法的,主要是因为不清楚哪个 if 条款下面的 else 条款将属于:

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。还要注意,可选的continuation子句总是以不能启动语句的关键字开头,因此没有歧义(’dangling else‘问题在Python中通过要求嵌套 if 语句缩进来解决)。

为了清楚起见,以下各节中的语法规则的格式化将每个子句放在单独的行上。

8.1. if 声明

if 语句用于条件执行:

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

它通过一个接一个地评估表达式,直到找到一个表达式为真(参见 布尔运算 的真和假定义部分)来选择一个套间。那么该套件被执行(并且没有执行或评估 if 语句的其他部分)。如果所有表达式都为假,则执行 else 子句的套件(如果存在)。

8.2. while 声明

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

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

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

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

8.3. for 声明

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

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

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

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

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

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 子句中没有发生异常时,不会执行异常处理程序。当 try 套件中发生异常时,将开始搜索异常处理程序。此搜索依次检查except子句,直到找到与异常匹配的子句。一个无表达式的except子句,如果存在,必须是last;它匹配任何异常。对于带有表达式的except子句,将评估该表达式,并且如果生成的对象与异常“兼容”,则子句与异常匹配。如果对象是类或异常对象的基类或包含与异常兼容的项的元组,则该对象与异常兼容。

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

如果对except子句的头部中的表达式求值引发异常,则取消对处理程序的原始搜索,并且对周围代码和调用堆栈中的新异常开始搜索(将其视为整个 try 声明提出了异常)。

当找到匹配的except子句时,异常被分配给在该except子句中的 as 关键字之后指定的目标(如果存在),并且执行except子句的套件。所有except子句必须具有可执行块。当达到此块的结束时,在整个try语句之后,执行正常。 (这意味着如果同一个异常存在两个嵌套处理程序,并且异常发生在内部处理程序的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() 返回一个由异常类,异常实例和一个跟踪对象(见第 标准类型层次结构 节)组成的三元组,用于标识程序中发生异常的点。当从处理异常的函数返回时,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 套件中执行时,finally 子句也在“出口”时执行。 continue 语句在 finally 子句中是非法的。 (原因是当前实现的问题 - 这个限制可能在将来解除)。

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

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

有关例外的更多信息可以在 例外 部分找到,关于使用 raise 语句生成例外的信息可以在 raise 声明 部分找到。

8.5. with 声明

with 语句用于使用由上下文管理器定义的方法来包装块的执行(参见 与语句上下文管理器 部分)。这允许公用的 try ... except ... finally 使用模式被封装,以方便重用。

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

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

  1. 评估上下文表达式(在 with_item 中给出的表达式)以获得上下文管理器。

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

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

  4. 如果目标包含在 with 语句中,则会将 __enter__() 的返回值赋给它。

    注解

    with 语句保证,如果 __enter__() 方法返回没有错误,则 __exit__() 将总是被调用。因此,如果在分配给目标列表期间发生错误,则将对其进行与在套件内发生的错误相同的处理。参见下面的步骤6。

  5. 套件被执行。

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

    如果套件由于异常而退出,并且 __exit__() 方法的返回值为false,则会重新处理异常。如果返回值为true,则异常被抑制,并且执行将继续执行 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 ("," defparameter)* ["," [parameter_list_starargs]]
                             | parameter_list_starargs
parameter_list_starargs ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                             | "**" parameter [","]
parameter               ::=  identifier [":" expression]
defparameter            ::=  parameter ["=" expression]
funcname                ::=  identifier

函数定义是一个可执行语句。它的执行将当前本地命名空间中的函数名绑定到函数对象(函数的可执行代码的包装器)。此函数对象包含对当前全局命名空间的引用,作为在调用函数时要使用的全局命名空间。

函数定义不执行函数体;这只有当函数被调用时才被执行。 [3]

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

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

大致相当于

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

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

当一个或多个 参数 具有 parameter = expression 的形式时,该函数被称为具有“默认参数值”。对于具有默认值的参数,可以从调用中省略相应的 argument,在这种情况下,将替换参数的默认值。如果参数具有默认值,则所有后续参数直到“ * ”也必须具有默认值—这是语法不表示的句法限制。

执行函数定义时,从左到右计算默认参数值。 这意味着表达式被计算一次,当函数被定义,并且相同的“预计算”值被用于每个调用。这对于理解默认参数是可变对象(例如列表或字典)时尤其重要:如果函数修改对象(例如,通过将项附加到列表),则默认值将被有效修改。这通常不是想要的。一种方法是使用 None 作为默认值,并在函数体中明确地测试它。:

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

函数调用语义在 呼叫 部分有更详细的描述。函数调用始终将参数值分配给参数列表中提到的所有参数,从位置参数,关键字参数或默认值。如果存在形式“ *identifier ”,则将其初始化为接收任何多余位置参数的元组,默认为空元组。如果存在形式“ **identifier ”,则它被初始化为接收任何多余关键字参数的新有序映射,默认为相同类型的新空映射。 “ * ”或“ *identifier ”之后的参数是仅关键字参数,并且只能使用关键字参数传递。

参数可以具有在参数名称后面的形式“ : 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

然后,该类的套件在新的执行框架(参见 命名和绑定)中执行,使用新创建的本地命名空间和原始的全局命名空间。 (通常,套件主要包含函数定义。)当类的套件完成执行时,它的执行框架被丢弃,但它的本地命名空间被保存。 [4] 然后使用基类的继承列表和属性字典的保存的本地命名空间创建类对象。类名称绑定到原始本地命名空间中的此类对象。

属性在类体中定义的顺序保留在新类的 __dict__ 中。注意,这只在类被创建之后才是可靠的,并且只适用于使用定义语法定义的类。

类创建可以使用 元类 进行大量定制。

类也可以装饰:就像装饰功能,

@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协程的执行可以暂停并在许多点恢复(参见 coroutine)。在协程的主体中,任何 awaitasync 标识符成为保留关键字; await 表达式,async forasync with 只能用于协同体。

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

它是一个 SyntaxErrorasync def 协程中使用 yield from 表达式。

协程函数的示例:

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

8.8.2. async for 声明

async_for_stmt ::=  "async" for_stmt

asynchronous iterable 能够在其 iter 实现中调用异步代码,并且 asynchronous iterator 可以在其 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__()

它是一个 SyntaxErrorasync def 函数之外使用 async for 语句。

8.8.3. async with 声明

async_with_stmt ::=  "async" with_stmt

asynchronous context manager 是能够在其 enterexit 方法中暂停执行的 context manager

下面的代码:

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__()

它是一个 SyntaxErrorasync def 函数之外使用 async with 语句。

参见

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

脚注

[1]

异常传播到调用堆栈,除非有一个 finally 子句发生另一个异常。这个新的异常导致旧的异常丢失。

[2]

目前,除非在异常或 returncontinuebreak 语句的执行的情况下,控制“流动结束”。

[3]

作为函数体中的第一个语句出现的字符串文字被转换为函数的 __doc__ 属性,因此函数的 docstring

[4]

显示为类主体中第一个语句的字符串文字被转换为命名空间的 __doc__ 项,因此转换为类的 docstring