8. Compound statements

复合语句包含(其他组)其他语句;它们以某种方式影响或控制其他语句的执行。通常,复合语句跨越多行,尽管在简单的形式上,整个复合语句可能包含在一行中。

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

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

if test1: if test2: print(x)

还要注意,在这种情况下,分号比冒号绑定更紧密,因此在以下示例中,将执行全部print()或不执行任何print()调用:

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

Summarizing:

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结尾。还要注意,可选的连续子句始终以无法开始语句的关键字开头,因此不会产生歧义(在 Python 中,pass要求嵌套的if语句缩进来解决“悬挂else”问题)。

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

8.1. if 语句

if语句用于条件执行:

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

它pass逐一评估表达式直到发现一个表达式为真,来选择其中的一个套件(关于正确与否的定义,请参见第Boolean operations节);然后执行该套件(并且不执行或不评估if语句的其他部分)。如果所有表达式均为假,则执行else子句的套件(如果存在)。

8.2. while 语句

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

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

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

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

8.3. for 语句

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

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

表达式列表被评估一次;它应该产生一个可迭代的对象。为expression_list的结果创建一个迭代器。然后按迭代器返回的 Sequences,对迭代器提供的每个项目执行一次套件。使用分配的标准规则(请参阅Assignment statements)将每个项目依次分配到目标列表,然后执行套件。当项目用尽时(紧接序列为空或迭代器引发StopIteration异常时),将执行else子句中的套件(如果存在),并且循环终止。

在第一个套件中执行的break语句终止循环,而不执行else子句的套件。在第一个套件中执行的continue语句将跳过套件的其余部分,并 continue 下一个项目,如果没有下一个项目,则 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]

Note

循环修改序列时会有些微妙(这仅适用于可变序列,例如列表)。内部计数器用于跟踪下一个要使用的项目,并且每次迭代时都会递增。当该计数器达到序列的长度时,循环终止。这意味着,如果套件从序列中删除当前(或上一个)项目,则下一个项目将被跳过(因为它获取已被处理的当前项目的索引)。同样,如果套件在当前项目之前按 Sequences 插入一个项目,则下次pass循环再次处理当前项目。这会导致令人讨厌的错误,可以pass使用整个序列的一部分进行临时复制来避免此类错误,例如,

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 子句(如果存在)必须在最后;它匹配任何异常。对于带有表达式的 except 子句,将对该表达式求值,并且如果结果对象与该异常“兼容”,则该子句与该异常匹配。如果对象是异常对象的类或 Base Class,或者是包含与异常兼容的项目的 Tuples,则该对象与异常兼容。

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

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

找到匹配的 except 子句后,会将异常分配给该 except 子句中as关键字之后指定的目标(如果存在),并执行 except 子句的套件。除条款外的所有子句都必须有一个可执行块。到达此块的末尾时,在整个 try 语句之后,正常 continue 执行。 (这意味着,如果针对同一异常存在两个嵌套处理程序,并且该异常发生在内部处理程序的 try 子句中,则外部处理程序将不会处理该异常.)

使用as target分配了异常后,将在 except 子句的末尾将其清除。好像

except E as N:
    foo

被翻译成

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

这意味着必须将异常分配给其他名称,以便能够在 except 子句之后引用该异常。清除异常是因为它们具有附加的回溯,它们与堆栈框架形成了一个参考循环,使该框架中的所有本地对象都保持活动状态,直到发生下一个垃圾回收为止。

在执行 except 子句的套件之前,有关异常的详细信息存储在sys模块中,可以passsys.exc_info()访问。 sys.exc_info()返回一个三 Tuples,该三 Tuples 由异常类,异常实例和回溯对象(请参见标准类型层次结构)组成,该对象标识程序中发生异常的点。从处理异常的函数返回时,sys.exc_info()值恢复为先前的值(在调用之前)。

如果控制流离开try套件,没有引发异常,并且没有执行returncontinuebreak语句,则执行可选的else子句。 else子句中的异常未由前面的except子句处理。

如果存在finally,则它指定一个“清理”处理程序。将执行try子句,包括任何exceptelse子句。如果在任何子句中发生异常并且未进行处理,则将临时保存该异常。 finally子句被执行。如果存在已保存的异常,它将在finally子句的末尾重新引发。如果finally子句引发另一个异常,则将保存的异常设置为新异常的上下文。如果finally子句执行returnbreakcontinue语句,则保存的异常将被丢弃:

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

在执行finally子句期间,异常信息不可用于程序。

当在tryfinally语句的try套件中执行returnbreakcontinue语句时,finally子句也将在“出路”上执行。

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

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

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

在 3.8 版中进行了更改:在 Python 3.8 之前,由于实现问题,在continue语句中的finally子句是非法的。

8.5. with 语句

with语句用于使用上下文 Management 器定义的方法来包装块的执行(请参见使用语句上下文 Management 器部分)。这允许将常见的tryexceptfinally用法模式封装起来,以方便重用。

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

带有一个“项目”的with语句的执行过程如下:

  • 计算上下文表达式(在with_item中给定的表达式)以获得上下文 Management 器。

  • 上下文 Management 器的enter()已加载以供以后使用。

  • 上下文 Management 器的exit()已加载以供以后使用。

  • 上下文 Management 器的enter()方法被调用。

  • 如果with语句中包含目标,则会将enter()的返回值分配给它。

Note

with语句保证如果enter()方法返回没有错误,则将始终调用exit()。因此,如果在分配给目标列表的过程中发生错误,则将其与套件中发生的错误相同。请参阅下面的步骤 6.

  • 该套件被执行。

  • 上下文 Management 器的exit()方法被调用。如果异常导致套件退出,则其类型,值和回溯将作为参数传递给exit()。否则,将提供三个None参数。

如果套件由于异常而退出,并且exit()方法的返回值为 false,则会引发异常。如果返回值是 true,则抑制异常,并从with语句后的语句 continue 执行。

如果套件是由于异常以外的其他原因退出的,则exit()的返回值将被忽略,并针对已采取的那种退出在正常位置执行。

如下代码:

with EXPRESSION as TARGET:
    SUITE

在语义上等效于:

manager = (EXPRESSION)
enter = type(manager).__enter__
exit = type(manager).__exit__
value = enter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not exit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        exit(manager, None, None, None)

如果有多个项目,则上下文 Management 器的处理就像嵌套了多个with语句:

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

在语义上等效于:

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

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

See also

  • PEP 343-“ with”语句

  • Python with语句的规范,背景和示例。

8.6. Function定义

函数定义定义了用户定义的函数对象(请参见标准类型层次结构):

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_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

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

函数定义不执行函数主体;仅在调用函数时执行此操作。 [2]

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

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

大致相当于

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

除了原始Function未临时绑定到名称func之外。

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

当执行函数定义时,默认参数值从左到右求值. 这意味着在定义函数时,对表达式进行一次求值,并且每次调用都使用相同的“预先计算”值。这对于理解默认参数是可变对象(例如列表或字典)时尤其重要:如果函数修改了该对象(例如,pass将项目附加到列表中),则默认值实际上已被修改。这通常不是预期的。解决此问题的一种方法是使用None作为默认值,并在函数主体中明确测试它,例如:

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

函数调用语义在Calls部分中有更详细的描述。函数调用总是将值分配给参数列表中提到的所有参数,无论是位置参数,关键字参数还是默认值。如果存在形式“ *identifier”,则将其初始化为接收任何多余位置参数的 Tuples,默认为空 Tuples。如果存在形式“ **identifier”,则将其初始化为一个新的有序 Map,该 Map 接收任何多余的关键字参数,默认为相同类型的新空 Map。 “ *”或“ *identifier”之后的参数仅是关键字参数,并且只能使用关键字参数传递。

参数名称后面可以带有形式为“ : expression”的annotation。任何参数都可以带有 Comments,即使是*identifier**identifier形式的 Comments。函数可能在参数列表之后具有“ -> expression”形式的“返回”Comments。这些 Comments 可以是任何有效的 Python 表达式。Comments 的存在不会改变函数的语义。Comments 值可用作以Function对象的__annotations__属性中的参数名称作为键的字典的值。如果使用了从future导入的annotations,则 Comments 将在运行时保留为字符串,从而可以推迟评估。否则,将在执行函数定义时对它们进行评估。在这种情况下,Comments 的评估 Sequences 可能与原始代码中出现的 CommentsSequences 不同。

还可以创建匿名函数(未绑定名称的函数),以便在表达式中立即使用。这将使用Lambdas部分中所述的 lambda 表达式。请注意,lambda 表达式只是简化函数定义的简写。就像 lambda 表达式定义的函数一样,“ def”语句中定义的函数可以传递或分配给另一个名称。 “ def”形式实际上更强大,因为它允许执行多个语句和 Comments。

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

See also

  • PEP 3107-Function Comments

  • 函数 Comments 的原始规范。

  • PEP 484-Importing 提示

  • Comments 的标准含义的定义:类型提示。

  • PEP 526-变量 Comments 的语法

  • 能够键入提示变量语句,包括类变量和实例变量

  • PEP 563-推迟评估 Comments

  • pass在运行时以字符串形式保留注解而不是急切求值,从而支持注解中的前向引用。

8.7. 类定义

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

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

类定义是可执行语句。继承列表通常会提供 Base Class 的列表(更多高级用法请参见Metaclasses),因此列表中的每个项目都应评估为一个允许子类化的类对象。默认情况下,没有继承列表的类从 Base Classobject继承;因此,

class Foo:
    pass

相当于

class Foo(object):
    pass

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

在类主体中定义属性的 Sequences 保留在新类的__dict__中。请注意,这仅在类创建之后并且仅对于使用定义语法定义的类是可靠的。

可以使用metaclasses自定义类的创建。

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

@f1(arg)
@f2
class Foo: pass

大致相当于

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

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

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

See also

  • PEP 3115-Python 3000 中的元类

  • 该提案将元类的语句更改为当前语法,以及有关如何构造具有元类的类的语义的建议。

  • PEP 3129-类装饰器

  • 建议添加类装饰器。在 PEP 318中引入了函数和方法修饰符。

8.8. Coroutines

3.5 版中的新Function。

8.8.1. 协程函数定义

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

Python 协程的执行可以在许多地方暂停和恢复(请参见coroutine)。在协程函数的内部,awaitasync标识符成为保留关键字; await表达式async forasync with只能在协程函数体中使用。

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

在协程函数的主体内部使用yield from表达式是SyntaxError

协程函数的示例:

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

8.8.2. 异步 for 语句

async_for_stmt ::=  "async" for_stmt

asynchronous iterable可以在其* iter 实现中调用异步代码,而asynchronous iterator可以在其 next *方法中调用异步代码。

async for语句允许pass异步迭代器方便地进行迭代。

如下代码:

async for TARGET in ITER:
    SUITE
else:
    SUITE2

在语义上等效于:

iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True

while running:
    try:
        TARGET = await type(iter).__anext__(iter)
    except StopAsyncIteration:
        running = False
    else:
        SUITE
else:
    SUITE2

有关详细信息,另请参见aiter()anext()

在协程函数的主体之外使用async for语句是SyntaxError

8.8.3. 异步 with 语句

async_with_stmt ::=  "async" with_stmt

异步上下文 Management 器context manager,可以pass其* enter exit *方法暂停执行。

如下代码:

async with EXPRESSION as TARGET:
    SUITE

在语义上等效于:

manager = (EXPRESSION)
aexit = type(manager).__aexit__
aenter = type(manager).__aenter__
value = await aenter(manager)
hit_except = False

try:
    TARGET = value
    SUITE
except:
    hit_except = True
    if not await aexit(manager, *sys.exc_info()):
        raise
finally:
    if not hit_except:
        await aexit(manager, None, None, None)

有关详细信息,另请参见aenter()aexit()

在协程函数的主体之外使用async with语句是SyntaxError

See also

  • PEP 492-具有异步和 await 语法的协程

  • 该提案使协程成为 Python 中适当的独立概念,并增加了支持语法。

Footnotes

  • [1]

    • 除非有一个finally子句碰巧引发另一个异常,否则该异常将传播到调用堆栈。新的异常导致旧的异常丢失。
  • [2]

    • 在函数体中显示为第一条语句的字符串 Literals 将转换为函数的__doc__属性,因此也转换为函数的docstring
  • [3]

    • 在类主体中显示为第一条语句的字符串 Literals 将转换为名称空间的__doc__项,因此也转换为类的docstring