7. Compound statements

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

ifwhilefor语句实现了传统的控制流构造。 try为一组语句指定异常处理程序和/或清除代码。函数和类定义在语法上也是复合语句。

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

if test1: if test2: print x

还要注意,在这种情况下,分号比冒号绑定更紧密,因此在以下示例中,将执行全部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
                   | decorated
suite         ::=  stmt_list NEWLINE | NEWLINE INDENT statement+ DEDENT
statement     ::=  stmt_list NEWLINE | compound_stmt
stmt_list     ::=  simple_stmt (";" simple_stmt)* [";"]

请注意,语句始终以NEWLINE结尾,并可能以DEDENT结尾。还要注意,可选的延续子句总是以不能开始语句的关键字开头,因此没有歧义(在 Python 中,“悬空else”问题pass要求嵌套的if语句缩进来解决)。

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

7.1. if 语句

if语句用于条件执行:

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

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

7.2. while 语句

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

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

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

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

7.3. for 语句

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

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

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

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

套件可以分配给目标列表中的变量。这不会影响分配给它的下一个项目。

循环结束时,不会删除目标列表,但是如果序列为空,则循环将根本不会将其分配给目标列表。提示:内置函数range()返回适合模拟 Pascal for i := a to b do效果的整数序列;例如range(3)返回列表[0, 1, 2]

Note

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

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" | ",") identifier]] ":" suite)+
               ["else" ":" suite]
               ["finally" ":" suite]
try2_stmt ::=  "try" ":" suite
               "finally" ":" suite

在版本 2.5 中进行了更改:在以前的 Python 版本中,tryexceptfinally无效。 tryexcept必须嵌套在tryfinally中。

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

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

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

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

在执行 except 子句的套件之前,将有关异常的详细信息分配给sys模块中的三个变量:sys.exc_type接收标识该异常的对象; sys.exc_value接收异常的参数; sys.exc_traceback接收一个 traceback 对象(请参见标准类型层次结构部分),该对象标识程序中发生异常的点。这些详细信息也可以passsys.exc_info()函数获得,该函数返回一个 Tuples(exc_type, exc_value, exc_traceback)。不赞成使用相应的变量,而推荐使用此Function,因为在线程程序中不安全地使用它们。从 Python 1.5 开始,从处理异常的函数返回时,变量将恢复为先前的值(在调用之前)。

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

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

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

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

tryfinally语句的try套件中执行returnbreakcontinue语句时,finally子句也将在“出路”上执行。 finally子句中的continue语句是非法的。 (原因是当前实施存在问题-将来可能会取消此限制)。

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

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

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

7.5. with 语句

2.5 版的新Function。

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

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

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

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

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

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

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

Note

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

  • 该套件被执行。

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

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

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

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

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

相当于

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

Note

在 Python 2.5 中,仅当启用with_statementFunction时才允许使用with语句。始终在 Python 2.6 中启用。

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

See also

  • PEP 343-“ with”语句

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

7.6. Function定义

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

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

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

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

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

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

等效于:

def func(): pass
func = f1(arg)(f2(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”,则它将初始化为一个新字典,该字典将接收任何多余的关键字参数,默认为一个新的空字典。

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

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

7.7. 类定义

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

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

类定义是可执行语句。它首先评估继承列表(如果存在)。继承列表中的每个项目都应评估为允许子类化的类对象或类类型。然后,使用新创建的本地名称空间和原始的全局名称空间在新的执行框架(请参见命名和绑定)中执行该类的套件。 (通常,套件仅包含函数定义.)当类的套件完成执行时,其执行框架将被丢弃,但其本地名称空间将被保存。 [3]然后,使用 Base Class 的继承列表和属性字典的已保存本地名称空间创建类对象。类名称绑定到原始本地名称空间中的该类对象。

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

类定义(如函数定义)可以由一个或多个decorator表达式包装。装饰器表达式的求值规则与函数相同。结果必须是一个类对象,然后将其绑定到类名称。

Footnotes

  • [1]

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

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

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