8. 复合语句¶
复合语句包含(组)其他语句;它们以某种方式影响或控制这些其他语句的执行。一般来说,复合语句跨越多行,尽管在简单的化身中,一整条复合语句可以包含在一行中。
这个 if , while 和 for 语句实现传统的控制流构造。 try 为一组语句指定异常处理程序和/或清理代码,而 with 语句允许在代码块周围执行初始化和终结代码。函数和类定义也是语法复合语句。
复合语句由一个或多个“子句”组成。“子句”由标题和“套件”组成。“特定复合语句的子句标题都位于同一缩进级别。”每个子句头以唯一标识关键字开头,以冒号结尾。套件是由子句控制的一组语句。一个套件可以是一个或多个分号分隔的简单语句,位于标题的同一行,在标题的冒号后面,也可以是后续行上的一个或多个缩进语句。只有后一种形式的套件可以包含嵌套的复合语句;以下是非法的,主要是因为不清楚 if 条款A以下 else 子句将属于:
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|async_with_stmt|async_for_stmt|async_funcdefsuite ::=stmt_listNEWLINE | NEWLINE INDENTstatement+ DEDENT statement ::=stmt_listNEWLINE |compound_stmtstmt_list ::=simple_stmt(";"simple_stmt)* [";"]
注意,语句总是以 NEWLINE 可能后面跟着一个 DEDENT . 还要注意,可选的延续子句总是以不能启动语句的关键字开头,因此没有歧义(悬空 else '问题在python中通过要求嵌套解决 if 语句缩进)。
以下部分中语法规则的格式设置将每个子句放在单独的行上,以便于清晰明了。
8.1. 这个 if 陈述¶
这个 if 语句用于条件执行:
if_stmt ::= "if"assignment_expression":"suite("elif"assignment_expression":"suite)* ["else" ":"suite]
它通过一个接一个地计算表达式,直到发现其中一个表达式是真的(参见第节 布尔运算 对于“真”和“假”的定义,则执行该套件(而不执行 if 语句被执行或计算)。如果所有表达式都为false,则 else 如有,执行条款。
8.2. 这个 while 陈述¶
这个 while 语句用于重复执行,只要表达式为真:
while_stmt ::= "while"assignment_expression":"suite["else" ":"suite]
这会反复测试表达式,如果为真,则执行第一个套件;如果表达式为假(可能是第一次测试),则执行 else 如果存在,则执行子句并终止循环。
A break 在第一个套件中执行的语句终止循环而不执行 else 条款的套件。A continue 在第一个套件中执行的语句跳过套件的其余部分,然后返回到测试表达式。
8.3. 这个 for 陈述¶
这个 for 语句用于迭代序列的元素(如字符串、元组或列表)或其他可iterable对象:
for_stmt ::= "for"target_list"in"expression_list":"suite["else" ":"suite]
表达式列表被计算一次;它应该产生一个可ITerable对象。为的结果创建迭代器 expression_list . 然后对迭代器提供的每个项执行一次该套件,顺序由迭代器返回。每个项目依次使用标准分配规则分配给目标列表(请参见 工作分配声明 ,然后执行该套件。当项用完时(当序列为空或迭代器引发 StopIteration 例外)中的套件 else 执行子句(如果存在),循环终止。
A break 在第一个套件中执行的语句终止循环而不执行 else 条款的套件。A 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() 返回一个整数迭代器,该迭代器适用于模拟帕斯卡的效果 for i := a to b do 例如 list(range(3)) 返回列表 [0, 1, 2] .
注解
当序列被循环修改时有一个微妙的地方(这只能发生在可变序列上,例如列表)。内部计数器用于跟踪下一个使用的项,并在每次迭代中递增。当这个计数器达到序列的长度时,循环终止。这意味着,如果套件从序列中删除当前(或上一个)项目,则将跳过下一个项目(因为它获取已处理的当前项目的索引)。同样,如果套件在当前项之前按顺序插入一个项,则下次将通过循环再次处理当前项。这可能会导致严重的错误,可以通过使用整个序列的切片进行临时复制来避免,例如::
for x in a[:]:
if x < 0: a.remove(x)
8.4. 这个 try 陈述¶
这个 try 语句指定一组语句的异常处理程序和/或清除代码:
try_stmt ::=try1_stmt|try2_stmttry1_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子句时,将异常分配给在 as 关键字,如果存在,则执行except子句的套件。所有except子句都必须具有可执行块。当到达该块的结尾时,在整个try语句之后,执行将正常继续。(这意味着如果同一个异常存在两个嵌套处理程序,并且该异常发生在内部处理程序的try子句中,则外部处理程序将不会处理该异常。)
当使用 as target ,在except子句的结尾处清除。这就好像是:
except E as N:
foo
已翻译为:
except E as N:
try:
foo
finally:
del N
这意味着必须将异常分配给不同的名称,才能在except子句之后引用它。异常被清除,因为当跟踪附加到它们时,它们与堆栈帧形成一个引用循环,使该帧中的所有局部变量保持活动状态,直到下一次垃圾收集发生。
在执行EXCEPT子句的套件之前,有关异常的详细信息存储在 sys 模块,并可通过以下方式访问 sys.exc_info() 。 sys.exc_info() 返回由Exception类、Exception实例和回溯对象组成的3元组(请参见部分 标准类型层次结构 )标识程序中发生异常的点。有关通过访问的异常的详细信息 sys.exc_info() 在离开异常处理程序时恢复为以前的值::
>>> print(sys.exc_info())
(None, None, None)
>>> try:
... raise TypeError
... except:
... print(sys.exc_info())
... try:
... raise ValueError
... except:
... print(sys.exc_info())
... print(sys.exc_info())
...
(<class 'TypeError'>, TypeError(), <traceback object at 0x10efad080>)
(<class 'ValueError'>, ValueError(), <traceback object at 0x10efad040>)
(<class 'TypeError'>, TypeError(), <traceback object at 0x10efad080>)
>>> print(sys.exc_info())
(None, None, None)
可选的 else 如果控制流离开 try 套件,没有引发异常,也没有 return , continue 或 break 语句已执行。中的例外 else 前条不处理子句 except 条款。
如果 finally 存在,它指定一个“清理”处理程序。这个 try 执行条款,包括 except 和 else 条款。如果在任何子句中发生异常但未处理,则会临时保存该异常。这个 finally 执行子句。如果存在已保存的异常,则在 finally 条款。如果 finally 子句引发另一个异常,保存的异常被设置为新异常的上下文。如果 finally 子句执行 return , break 或 continue 语句,已保存的异常将被丢弃::
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行 finally 条款。
当A return , break 或 continue 语句在中执行 try A套间 try …… finally 语句 finally 条款也在“退出时”执行。
函数的返回值由最后一个 return 语句已执行。自从 finally 始终执行子句,a return 在中执行的语句 finally 子句将始终是最后一个执行的子句::
>>> def foo():
... try:
... return 'try'
... finally:
... return 'finally'
...
>>> foo()
'finally'
有关异常的其他信息,请参见第节。 例外情况 以及有关使用 raise 生成异常的语句可以在节中找到 这个 raise 陈述 .
8.5. 这个 with 陈述¶
这个 with 语句用于用上下文管理器定义的方法封装块的执行(请参见 使用语句上下文管理器 )这使得 try …… except …… finally 要封装的使用模式以方便重用。
with_stmt ::= "with" ( "(" with_stmt_contents ","? ")" | with_stmt_contents ) ":" suite
with_stmt_contents ::= with_item ("," with_item)*
with_item ::= expression ["as" target]
执行 with 有一个“项目”的声明如下:
上下文表达式(在
with_item)评估以获取上下文管理器。上下文管理器的
__enter__()已加载以供以后使用。上下文管理器的
__exit__()已加载以供以后使用。上下文管理器的
__enter__()方法被调用。如果目标包含在
with语句,返回值来自__enter__()分配给它。注解
这个
with声明保证如果__enter__()方法返回时不出错,然后__exit__()将始终被调用。因此,如果在分配给目标列表的过程中发生错误,则会将其视为套件中发生的错误。请参见下面的步骤6。执行套件。
上下文管理器的
__exit__()方法被调用。如果异常导致套件退出,则将其类型、值和回溯作为参数传递给__exit__(). 否则,三个None提供了参数。如果由于异常退出套件,并且
__exit__()方法为false,将重新引发异常。如果返回值为true,则会抑制异常,并继续执行以下语句:with语句。如果套件出于异常以外的任何原因退出,则返回值为
__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)
对于多个项目,上下文管理器的处理方式就像多个项目一样 with 语句被嵌套::
with A() as a, B() as b:
SUITE
在语义上等同于:
with A() as a:
with B() as b:
SUITE
如果项目用括号括起来,您还可以在多行中编写多项目上下文管理器。例如::
with (
A() as a,
B() as b,
):
SUITE
在 3.1 版更改: 支持多个上下文表达式。
在 3.10 版更改: 支持使用分组圆括号将语句分成多行。
8.6. 函数定义¶
函数定义定义用户定义的函数对象(参见第节 标准类型层次结构 ):
funcdef ::= [decorators] "def"funcname"(" [parameter_list] ")" ["->"expression] ":"suitedecorators ::=decorator+ decorator ::= "@"assignment_expressionNEWLINE dotted_name ::=identifier("."identifier)* parameter_list ::=defparameter(","defparameter)* "," "/" ["," [parameter_list_no_posonly]] |parameter_list_no_posonlyparameter_list_no_posonly ::=defparameter(","defparameter)* ["," [parameter_list_starargs]] |parameter_list_starargsparameter_list_starargs ::= "*" [parameter] (","defparameter)* ["," ["**"parameter[","]]] | "**"parameter[","] parameter ::=identifier[":"expression] defparameter ::=parameter["="expression] funcname ::=identifier
函数定义是一个可执行语句。它的执行将当前本地命名空间中的函数名绑定到函数对象(函数可执行代码的封装器)。此函数对象包含对当前全局命名空间的引用,作为调用函数时要使用的全局命名空间。
函数定义不执行函数体;只有在调用函数时才会执行。 2
函数定义可以由一个或多个 decorator 表达。当在包含函数定义的作用域中定义函数时,将计算装饰表达式。结果必须是可调用的,它是以函数对象作为唯一参数调用的。返回的值绑定到函数名,而不是函数对象。多个装饰器以嵌套方式应用。例如,以下代码:
@f1(arg)
@f2
def func(): pass
大致相当于:
def func(): pass
func = f1(arg)(f2(func))
但原始函数没有临时绑定到名称 func .
在 3.9 版更改: 函数可以用任何有效的 assignment_expression . 以前,语法限制要严格得多;参见 PEP 614 有关详细信息。
当一个或多个 parameters 有形式 参数 = 表达 ,函数被称为具有“默认参数值”。对于具有默认值的参数,对应的 argument 可以从调用中省略,在这种情况下,将替换参数的默认值。如果一个参数有一个默认值,那么在“*`”之前的所有后续参数都必须有一个默认值---这是语法中不表示的句法限制。
执行函数定义时,从左到右计算默认参数值。 这意味着在定义函数时,表达式将被计算一次,并且每次调用都使用相同的“预计算”值。当默认参数值是可变对象(如列表或字典)时,这一点尤其重要:如果函数修改了对象(例如,通过向列表中添加一个项),则默认参数值实际上已被修改。这通常不是我们的初衷。解决这个问题的方法是使用 None 作为默认值,并在函数体中显式测试它,例如:
def whats_on_the_telly(penguin=None):
if penguin is None:
penguin = []
penguin.append("property of the zoo")
return penguin
函数调用语义在第节中有更详细的描述。 调用 . 函数调用总是为参数列表中提到的所有参数赋值,无论是从位置参数、关键字参数还是从默认值。如果表格“” identifier``" is present, it is initialized to a tuple receiving any excess positional parameters, defaulting to the empty tuple. If the form "`` identifier``" is present, it is initialized to a new ordered mapping receiving any excess keyword arguments, defaulting to a new empty mapping of the same type. Parameters after "`` “或”``*identifier`'是仅关键字参数,只能使用关键字参数传递。
参数可以具有 annotation 参数名后面的形式为“:expression”。任何参数都可以有批注,即使是以下形式的批注也是如此 *identifier 或 **identifier 。函数在参数列表后可以有形式为“->表达式”的“返回”注释。这些注释可以是任何有效的Python表达式。注释的存在不会改变函数的语义。注释值在字典中以字符串值的形式提供,该字典以 __annotations__ 函数对象的属性。
还可以创建匿名函数(未绑定到名称的函数),以便在表达式中立即使用。这将使用lambda表达式,如第节所述。 兰姆达斯 . 请注意,lambda表达式只是简化函数定义的简写;可以像lambda表达式定义的函数一样,将 "def" 语句中定义的函数传递或分配给另一个名称。 "def" 表单实际上更强大,因为它允许执行多个语句和注释。
编程器说明: 函数是一流的对象。在函数定义内执行的 "def" 语句定义了可以返回或传递的本地函数。嵌套函数中使用的自由变量可以访问包含def的函数的局部变量。见节 名字与绑定 有关详细信息。
8.7. 类定义¶
类定义定义定义类对象(参见第节 标准类型层次结构 ):
classdef ::= [decorators] "class"classname[inheritance] ":"suiteinheritance ::= "(" [argument_list] ")" classname ::=identifier
类定义是一个可执行语句。继承列表通常给出一个基类列表(请参见 元类 对于更高级的用法),因此列表中的每个项都应该计算为允许子类化的类对象。没有继承列表的类默认从基类继承 object 因此,:
class Foo:
pass
等于:
class Foo(object):
pass
然后在新的执行框架中执行类的套件(请参见 名字与绑定 ,使用新创建的本地命名空间和原始全局命名空间。(通常,该套件主要包含函数定义。)当类的套件完成执行时,将放弃其执行帧,但保存其本地命名空间。 3 然后使用基本类的继承列表和属性字典保存的本地命名空间创建类对象。类名在原始本地命名空间中绑定到此类对象。
在类体中定义属性的顺序保留在新类的 __dict__ . 请注意,这仅在创建类之后才是可靠的,并且仅适用于使用定义语法定义的类。
类创建可以大量使用自定义 metaclasses .
类也可以修饰:就像修饰函数一样,::
@f1(arg)
@f2
class Foo: pass
大致相当于:
class Foo: pass
Foo = f1(arg)(f2(Foo))
decorator表达式的计算规则与函数decorator的计算规则相同。然后将结果绑定到类名。
在 3.9 版更改: 类可以用任何有效的 assignment_expression . 以前,语法限制要严格得多;参见 PEP 614 有关详细信息。
编程器说明: 类定义中定义的变量是类属性;它们由实例共享。实例属性可以在具有 self.name = value . 类和实例属性都可以通过符号“`` self.name``”访问,并且实例属性在以这种方式访问时隐藏具有相同名称的类属性。类属性可以用作实例属性的默认值,但使用可变值可能会导致意外的结果。 Descriptors 可用于创建具有不同实现详细信息的实例变量。
8.8. 协同程序¶
3.5 新版功能.
8.8.1. 协程函数定义¶
async_funcdef ::= [decorators] "async" "def"funcname"(" [parameter_list] ")" ["->"expression] ":"suite
可以在多个点暂停和恢复Python协同例程的执行(请参见 coroutine )。 await 表情, async for 和 async with 只能在协程函数的主体中使用。
函数定义为 async def 语法始终是协同函数,即使它们不包含 await 或 async 关键词。
这是一个 SyntaxError 使用A yield from 在协程函数体内部的表达式。
协程函数的示例:
async def func(param1, param2):
do_stuff()
await some_coroutine()
在 3.7 版更改: await 和 async 现在是关键字;以前,它们只在协程函数的主体内被视为关键字。
8.8.2. 这个 async for 陈述¶
async_for_stmt ::= "async" for_stmt
一个 asynchronous iterable 提供了一个 __aiter__ 方法,该方法直接返回 asynchronous iterator ,它可以调用其 __anext__ 方法。
这个 async for 语句允许在异步迭代器上进行方便的迭代。
以下代码:
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__() 有关详细信息。
这是一个 SyntaxError 使用一个 async for 语句位于协程函数体外部。
8.8.3. 这个 async with 陈述¶
async_with_stmt ::= "async" with_stmt
安 asynchronous context manager 是一个 context manager 能够在其 进入 和 exit 方法。
以下代码:
async with EXPRESSION as TARGET:
SUITE
在语义上等同于:
manager = (EXPRESSION)
aenter = type(manager).__aenter__
aexit = type(manager).__aexit__
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__() 有关详细信息。
这是一个 SyntaxError 使用一个 async with 语句位于协程函数体外部。
参见
- PEP 492 -带有异步和等待语法的协程
这个提议使协同程序在Python中成为一个适当的独立概念,并添加了支持语法。
脚注