On this page
7. Compound statements
复合语句包含(其他组)其他语句;它们以某种方式影响或控制其他语句的执行。通常,复合语句跨越多行,尽管在简单的形式上,整个复合语句可能包含在一行中。
if,while和for语句实现了传统的控制流构造。 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 版本中,try…except…finally无效。 try…except必须嵌套在try…finally中。
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套件,没有引发异常,并且没有执行return,continue或break语句,则执行可选的else子句。 else子句中的异常未由前面的except子句处理。
如果存在finally,则它指定一个“清理”处理程序。将执行try子句,包括任何except和else子句。如果在任何子句中发生异常并且未进行处理,则将临时保存该异常。 finally子句被执行。如果存在已保存的异常,则会在finally子句的末尾重新引发该异常。如果finally子句引发另一个异常或执行return或break语句,则保存的异常将被丢弃:
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行finally子句期间,异常信息不可用于程序。
在try…finally语句的try套件中执行return,break或continue语句时,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 器部分)。这允许将常见的try…except…finally用法模式封装起来,以方便重用。
with_stmt ::= "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
带有一个“项目”的with语句的执行过程如下:
计算上下文表达式(在with_item中给定的表达式)以获得上下文 Management 器。
上下文 Management 器的exit()已加载以供以后使用。
上下文 Management 器的enter()方法被调用。
如果套件由于异常而退出,并且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_statement
Function时才允许使用with语句。始终在 Python 2.6 中启用。
在 2.7 版中进行了更改:支持多个上下文表达式。
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