On this page
8. Compound statements
复合语句包含(其他组)其他语句;它们以某种方式影响或控制其他语句的执行。通常,复合语句跨越多行,尽管在简单的形式上,整个复合语句可能包含在一行中。
if,while和for语句实现了传统的控制流构造。 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套件,没有引发异常,并且没有执行return,continue或break语句,则执行可选的else
子句。 else
子句中的异常未由前面的except子句处理。
如果存在finally,则它指定一个“清理”处理程序。将执行try子句,包括任何except和else
子句。如果在任何子句中发生异常并且未进行处理,则将临时保存该异常。 finally
子句被执行。如果存在已保存的异常,它将在finally
子句的末尾重新引发。如果finally
子句引发另一个异常,则将保存的异常设置为新异常的上下文。如果finally
子句执行return,break或continue语句,则保存的异常将被丢弃:
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行finally子句期间,异常信息不可用于程序。
当在try
…finally
语句的try套件中执行return,break或continue语句时,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 器部分)。这允许将常见的try…except…finally用法模式封装起来,以方便重用。
with_stmt ::= "with" with_item ("," with_item)* ":" suite
with_item ::= expression ["as" target]
带有一个“项目”的with语句的执行过程如下:
计算上下文表达式(在with_item中给定的表达式)以获得上下文 Management 器。
上下文 Management 器的enter()已加载以供以后使用。
上下文 Management 器的exit()已加载以供以后使用。
上下文 Management 器的enter()方法被调用。
如果套件由于异常而退出,并且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 中进行了更改:支持多个上下文表达式。
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
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
8.8. Coroutines
3.5 版中的新Function。
8.8.1. 协程函数定义
async_funcdef ::= [decorators] "async" "def" funcname "(" [parameter_list] ")"
["->" expression] ":" suite
Python 协程的执行可以在许多地方暂停和恢复(请参见coroutine)。在协程函数的内部,await
和async
标识符成为保留关键字; await表达式async for和async with只能在协程函数体中使用。
用async def
语法定义的函数始终是协程函数,即使它们不包含await
或async
关键字。
在协程函数的主体内部使用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
在协程函数的主体之外使用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)
在协程函数的主体之外使用async with
语句是SyntaxError。
See also
PEP 492-具有异步和 await 语法的协程
该提案使协程成为 Python 中适当的独立概念,并增加了支持语法。
Footnotes