7. 复合语句 — Python 文档
7. 复合语句
复合语句包含(组)其他语句; 它们以某种方式影响或控制其他语句的执行。 通常,复合语句跨越多行,尽管在简单的化身中,整个复合语句可能包含在一行中。
if、while 和 for 语句实现了传统的控制流结构。 try 为一组语句指定异常处理程序和/或清理代码。 函数和类定义也是语法上的复合语句。
复合语句由一个或多个“子句”组成。 子句由标题和“套件”组成。 特定复合语句的子句标题都处于相同的缩进级别。 每个子句标题都以唯一标识关键字开始,并以冒号结束。 套件是由子句控制的一组语句。 一个套件可以是一个或多个分号分隔的简单语句,与标题位于同一行,跟随标题的冒号,或者它可以是后续行中的一个或多个缩进语句。 只有后一种形式的套件才能包含嵌套的复合语句; 以下是非法的,主要是因为不清楚 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 | decorated 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 语句解决缩进)。
为清楚起见,以下各节中语法规则的格式将每个子句放在单独的行中。
7.1. 这如果陈述
if 语句用于条件执行:
if_stmt ::= "if" expression ":" suite ( "elif" expression ":" suite )* ["else" ":" suite]
它通过对表达式一一求值直到发现其中一个为真来精确地选择其中一个套件(有关真假的定义,请参见布尔运算部分); 然后执行该套件(并且不执行或评估 if 语句的其他部分)。 如果所有表达式都为假,则执行 else 子句套件(如果存在)。
7.2. 这尽管陈述
while 语句用于重复执行,只要表达式为真:
while_stmt ::= "while" expression ":" suite ["else" ":" suite]
这会反复测试表达式,如果为真,则执行第一个套件; 如果表达式为假(这可能是第一次测试),则执行 else 子句套件(如果存在)并终止循环。
在第一个套件中执行的 break 语句终止循环而不执行 else 子句的套件。 在第一个套件中执行的 continue 语句跳过套件的其余部分并返回测试表达式。
7.3. 这为了陈述
for 语句用于迭代序列(例如字符串、元组或列表)或其他可迭代对象的元素:
for_stmt ::= "for" target_list "in" expression_list ":" suite ["else" ":" suite]
表达式列表被评估一次; 它应该产生一个可迭代的对象。 为 expression_list
的结果创建迭代器。 然后按照索引升序对迭代器提供的每个项目执行一次套件。 使用标准分配规则依次将每个项目分配给目标列表,然后执行套件。 当项目用完时(即当序列为空时),执行 else 子句中的套件(如果存在),并且循环终止。
在第一个套件中执行的 break 语句终止循环而不执行 else 子句的套件。 在第一个套件中执行的 continue 语句跳过套件的其余部分并继续下一个项目,如果没有下一个项目,则使用 else 子句。
该套件可以分配给目标列表中的变量; 这不会影响分配给它的下一个项目。
循环结束时不会删除目标列表,但如果序列为空,则循环根本不会将其分配给它。 提示:内置函数 range() 返回适合模拟 Pascal 的 for i := a to b do
效果的整数序列; 例如,range(3)
返回列表 [0, 1, 2]
。
笔记
当循环修改序列时有一个微妙之处(这只能发生在可变序列,例如 列表)。 内部计数器用于跟踪下一个使用哪个项目,并且在每次迭代时递增。 当这个计数器达到序列的长度时,循环终止。 这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。 同样,如果套件在当前项目之前的序列中插入一个项目,则当前项目将在下一次循环中再次被处理。 这可能会导致令人讨厌的错误,可以通过使用整个序列的一部分制作临时副本来避免这些错误,例如,
for x in a[:]:
if x < 0: a.remove(x)
7.4. 这尝试陈述
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 子句,该表达式被计算,如果结果对象与异常“兼容”,则该子句匹配异常。 如果对象是异常对象的类或基类,或者包含与异常兼容的项目的元组,则该对象与异常兼容。
如果没有except 子句与异常匹配,则在周围代码和调用堆栈中继续搜索异常处理程序。 1
如果对 except 子句头中的表达式求值引发异常,则取消对处理程序的原始搜索,并开始在周围代码和调用堆栈中搜索新异常(它被视为整个try 语句引发了异常)。
当找到匹配的 except 子句时,将异常分配给该 except 子句中指定的目标(如果存在),并执行 except 子句的套件。 所有except子句都必须有一个可执行块。 当到达该块的末尾时,在整个 try 语句之后继续正常执行。 (这意味着如果对于同一个异常存在两个嵌套的处理程序,并且异常发生在内部处理程序的 try 子句中,外部处理程序将不会处理该异常。)
在执行except 子句的套件之前,有关异常的详细信息被分配给sys 模块中的三个变量: sys.exc_type
接收标识异常的对象; sys.exc_value
接收异常参数; sys.exc_traceback
接收一个回溯对象(参见 标准类型层次结构 部分),标识程序中发生异常的点。 这些详细信息也可以通过 sys.exc_info() 函数获得,该函数返回一个元组 (exc_type, exc_value, exc_traceback)
。 不推荐使用相应的变量而支持此函数,因为它们在线程程序中的使用是不安全的。 从 Python 1.5 开始,当从处理异常的函数返回时,变量将恢复到它们以前的值(调用之前)。
如果控制流离开 try 套件,没有引发异常,并且没有 return,continue,则执行可选的 else 子句,或 break 语句被执行。 else 子句中的异常不会由前面的 except 子句处理。
如果 finally 存在,它指定一个“清理”处理程序。 执行 try 子句,包括任何 except 和 else 子句。 如果在任何一个子句中发生异常并且没有被处理,则该异常被临时保存。 finally 子句被执行。 如果有保存的异常,它会在 finally 子句的末尾重新引发。 如果 finally 子句引发另一个异常或执行 return 或 break 语句,则丢弃保存的异常:
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行 finally 子句期间,程序无法获得异常信息。
当 return、break 或 continue 语句在 try… 的 try 套件中执行时]finally 语句,finally 子句也在“出路”中执行。 continue 语句在 finally 子句中是非法的。 (原因是目前的实现有问题——这个限制可能会在未来取消)。
函数的返回值由最后执行的 return 语句决定。 由于 finally 子句始终执行,因此在 finally 子句中执行的 return 语句将始终是最后执行的:
>>> def foo():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> foo()
'finally'
关于异常的更多信息可以在 Exceptions 部分找到,关于使用 raise 语句生成异常的信息可以在 raise 语句 部分找到。
7.5. 这和陈述
2.5 版中的新功能。
with 语句用于用上下文管理器定义的方法包装块的执行(参见 With 语句上下文管理器 部分)。 这允许封装常见的 try...except...finally 使用模式,以便于重用。
with_stmt ::= "with" with_item ("," with_item)* ":" suite with_item ::= expression ["as" target]
带有一个“项目”的 with 语句的执行过程如下:
对上下文表达式(with_item 中给出的表达式)求值以获得上下文管理器。
上下文管理器的
__exit__()
被加载以备后用。上下文管理器的
__enter__()
方法被调用。如果目标包含在 with 语句中,则将
__enter__()
的返回值分配给它。笔记
with 语句保证如果
__enter__()
方法返回没有错误,那么__exit__()
将始终被调用。 因此,如果在分配给目标列表的过程中发生错误,它将被视为套件中发生的错误。 请参阅下面的步骤 6。套房被执行。
上下文管理器的
__exit__()
方法被调用。 如果异常导致套件退出,则其类型、值和回溯将作为参数传递给__exit__()
。 否则,提供三个 None 参数。如果套件因异常退出,并且
__exit__()
方法的返回值为 false,则重新引发异常。 如果返回值为真,则异常被抑制,并继续执行 with 语句之后的语句。如果套件因异常以外的任何原因退出,则忽略来自
__exit__()
的返回值,并在所采取的退出类型的正常位置继续执行。
对于不止一项,上下文管理器的处理就像嵌套了多个 with 语句:
with A() as a, B() as b:
suite
相当于
with A() as a:
with B() as b:
suite
2.7 版更改: 支持多上下文表达式。
7.6. 功能定义
一个函数定义定义了一个用户定义的函数对象(参见 标准类型层次结构 部分):
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
函数定义是一个可执行语句。 它的执行将当前本地命名空间中的函数名称绑定到一个函数对象(函数的可执行代码的包装器)。 该函数对象包含对当前全局命名空间的引用,作为调用该函数时要使用的全局命名空间。
函数定义不执行函数体; this 仅在调用函数时执行。 2
一个函数定义可以被一个或多个 decorator 表达式包裹。 在定义函数时,在包含函数定义的范围内评估装饰器表达式。 结果必须是可调用的,以函数对象作为唯一参数调用。 返回值绑定到函数名而不是函数对象。 多个装饰器以嵌套方式应用。 例如,以下代码:
@f1(arg)
@f2
def func(): pass
相当于:
def func(): pass
func = f1(arg)(f2(func))
当一个或多个顶级 参数 具有 参数 =
表达式 的形式时,该函数被称为具有“默认参数值”。 对于具有默认值的参数,可以从调用中省略相应的 参数 ,在这种情况下,参数的默认值将被替换。 如果一个参数有一个默认值,那么后面的所有参数也必须有一个默认值——这是语法没有表达的句法限制。
执行函数定义时评估默认参数值。这意味着表达式被评估一次,当函数被定义时,并且每次调用都使用相同的“预计算”值。 这对于理解默认参数何时是可变对象(例如列表或字典)尤其重要:如果函数修改了对象(例如 通过将项目附加到列表),实际上修改了默认值。 这通常不是预期的。 解决这个问题的一种方法是使用 None
作为默认值,并在函数体中明确测试它,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
函数调用语义在 Calls 部分有更详细的描述。 函数调用始终为参数列表中提到的所有参数赋值,无论是位置参数、关键字参数还是默认值。 如果存在“*identifier
”形式,则将其初始化为接收任何多余位置参数的元组,默认为空元组。 如果存在“**identifier
”形式,则将其初始化为接收任何多余关键字参数的新字典,默认为新的空字典。
还可以创建匿名函数(未绑定到名称的函数),以便在表达式中立即使用。 这使用了 lambda 表达式,在 Lambdas 部分中进行了描述。 请注意,lambda 表达式只是简化函数定义的简写; 在“def”语句中定义的函数可以传递或分配给另一个名称,就像由 lambda 表达式定义的函数一样。 “def”形式实际上更强大,因为它允许执行多个语句。
程序员注:函数是一等对象。 在函数定义中执行的“def
”形式定义了一个可以返回或传递的局部函数。 嵌套函数中使用的自由变量可以访问包含 def 的函数的局部变量。 有关详细信息,请参阅 命名和绑定 部分。
7.7. 类定义
类定义定义了一个类对象(参见 标准类型层次结构 部分):
classdef ::= "class" classname [inheritance] ":" suite inheritance ::= "(" [expression_list] ")" classname ::= identifier
类定义是一个可执行语句。 它首先评估继承列表(如果存在)。 继承列表中的每一项都应评估为允许子类化的类对象或类类型。 然后使用新创建的本地命名空间和原始全局命名空间在新的执行框架中执行该类的套件(参见 命名和绑定 部分)。 (通常,套件只包含函数定义。)当类的套件完成执行时,它的执行帧会被丢弃,但它的本地命名空间会被保存。 3 然后使用基类的继承列表和为属性字典保存的本地命名空间创建类对象。 类名绑定到原始本地命名空间中的这个类对象。
程序员注:类定义中定义的变量为类变量; 它们由所有实例共享。 要创建实例变量,可以使用 self.name = value
在方法中设置它们。 类变量和实例变量都可以通过符号“self.name
”访问,这样访问时,实例变量隐藏了同名的类变量。 类变量可以用作实例变量的默认值,但在那里使用可变值可能会导致意外结果。 对于 new-style classes,描述符可用于创建具有不同实现细节的实例变量。
类定义与函数定义一样,可以由一个或多个 decorator 表达式包装。 装饰器表达式的计算规则与函数相同。 结果必须是一个类对象,然后绑定到类名。
脚注
- 1
- 除非有 finally 子句碰巧引发另一个异常,否则异常将传播到调用堆栈。 新的异常会导致旧的异常丢失。
- 2
- 作为函数体中第一条语句出现的字符串文字被转换为函数的
__doc__
属性,因此是函数的 docstring。 - 3
- 作为类主体中第一条语句出现的字符串文字被转换为命名空间的
__doc__
项,因此转换为类的 docstring。