复合语句的声明(控制流结构)(8)Python语言(语法教程)(参考资料)
复合语句包含(组)其他语句; 它们以某种方式影响或控制其他陈述的执行。通常,复合语句跨越多行,但在简单的化身中,整个复合语句可以包含在一行中。
的if
,while
和for
语句实现了传统的控制流结构。 try
为一组语句指定异常处理程序和/或清理代码,而该 with
语句允许围绕代码块执行初始化和完成代码。函数和类定义也是语法复合语句。
复合语句由一个或多个“子句”组成。子句由标题和“套件”组成。特定复合语句的子句标题都在相同的缩进级别。每个子句头以唯一标识关键字开头,以冒号结束。套件是由子句控制的一组语句。套件可以是与标题位于同一行的一个或多个以分号分隔的简单语句,位于标题的冒号后面,也可以是后续行中的一个或多个缩进语句。只有后一种形式的套件才能包含嵌套的复合语句; 以下是非法的,主要是因为if
以下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 | 套房 :: = NEWLINE | NEWLINE INDENT + DEDENT 声明 :: = NEWLINE | stmt_list :: = (“;” )* [“;”] async_funcdef stmt_liststatementstmt_listcompound_stmt simple_stmtsimple_stmt
请注意,语句总是以a NEWLINE
后跟a 结尾DEDENT
。还要注意,可选的continuation子句总是以一个无法启动语句的关键字开头,因此没有歧义(else
通过要求嵌套if
语句缩进,在Python中解决了“悬空”问题 )。
为清楚起见,以下各节中语法规则的格式将每个子句放在单独的行上。
8.1。该if
声明
该if
语句用于条件执行:
if_stmt :: =“if” expression“:” suite (“elif” expression“:” suite)* [“else”“:” suite]
它通过逐个评估表达式来选择其中一个套件,直到找到一个为真(参见布尔运算中的true和false的定义); 然后执行该套件(并且不if
执行或评估该语句的其他部分 )。如果所有表达式都为false else
,则执行该子句的套件(如果存在)。
8.2。该while
声明
while
只要表达式为真,该语句就用于重复执行:
while_stmt :: =“while” expression“:” suite [“else”“:” suite]
这反复测试表达式,如果是,则执行第一个套件; 如果表达式为假(可能是第一次测试)else
,则执行该子句的套件(如果存在)并且循环终止。
一个break
在首套房执行的语句终止循环,不执行该else
条款的套件。continue
在第一个套件中执行的语句会跳过套件的其余部分,然后返回测试表达式。
8.3。该for
声明
该for
语句用于迭代序列的元素(例如字符串,元组或列表)或其他可迭代对象:
for_stmt :: =“for” target_list“in” expression_list“:” suite [“else”“:” suite]
表达式列表评估一次; 它应该产生一个可迭代的对象。为结果创建一个迭代器expression_list
。然后,按迭代器返回的顺序,对迭代器提供的每个项执行一次该套件。依次使用标准的赋值规则将每个项目分配给目标列表(请参阅赋值语句),然后执行套件。当项目耗尽时(当序列为空或迭代器引发StopIteration
异常时立即),else
子句中的套件(如果存在)将被执行,并且循环终止。
一个break
在首套房执行的语句终止循环,不执行该else
条款的套件。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]
注意
当循环修饰序列时有一个微妙之处(这只能发生在可变序列中,例如列表)。内部计数器用于跟踪下一个使用的项目,并在每次迭代时递增。当该计数器达到序列的长度时,循环终止。这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已经处理的当前项目的索引)。同样,如果套件在当前项目之前的序列中插入项目,则下次循环时将再次处理当前项目。这可能导致令人讨厌的错误,可以通过使用整个序列的切片进行临时复制来避免,例如,
for x in a[:]:
if x < 0:
a.remove(x)
8.4。该try
声明
该try
语句为一组语句指定异常处理程序和/或清理代码:
try_stmt :: = try1_stmt| try1_stmt :: =“try”“:” (“除了”[ [“as” ]]“:” )+try2_stmt suiteexpressionidentifiersuite [“else”“:”suite] [“finally”“:” suite] try2_stmt :: =“try”“:” suite “finally”“:”suite
suite
该except
条款(S)指定一个或多个异常处理程序。当try
子句中没有异常发生时,不执行异常处理程序。当try
套件中发生异常时,将启动对异常处理程序的搜索。此搜索依次检查except子句,直到找到与该异常匹配的子句。无表达式的except子句(如果存在)必须是最后一个; 它匹配任何异常。对于带有表达式的except子句,将计算该表达式,如果结果对象与异常“兼容”,则子句匹配该异常。如果对象是异常对象的类或基类,或者包含与异常兼容的项的元组,则该对象与异常兼容。
如果没有except子句匹配异常,则在周围代码和调用堆栈中继续搜索异常处理程序。 [1]
如果对except子句的标头中的表达式的求值引发异常,则会取消对处理程序的原始搜索,并在周围代码和调用堆栈中开始搜索新异常(它被视为整个try
声明提出异常)。
找到匹配的except子句时,将异常分配给as
在该except子句中的关键字之后指定的目标(如果存在),并执行except子句的套件。所有except子句必须具有可执行块。到达此块的末尾时,在整个try语句之后正常执行。(这意味着如果同一个异常存在两个嵌套处理程序,并且内部处理程序的try子句中发生异常,则外部处理程序将不处理异常。)
使用时分配异常时,将在except子句的末尾清除它。这就好像as target
except E as N:
foo
被翻译成
except E as N:
try:
foo
finally:
del N
这意味着必须将异常分配给不同的名称才能在except子句之后引用它。异常被清除,因为附加了回溯,它们与堆栈帧形成一个引用循环,使该帧中的所有本地生存,直到下一次垃圾收集发生。
在执行except子句的套件之前,有关异常的详细信息存储在sys
模块中,可以通过访问sys.exc_info()
。sys.exc_info()
返回一个3元组,由异常类,异常实例和一个traceback对象组成(请参阅标准类型层次结构一节),标识发生异常的程序中的点。 sys.exc_info()
从处理异常的函数返回时,值将恢复为先前的值(在调用之前)。
可选的else
条款是,如果控制流离开执行 try
套件,没有出现异常,没有return
, continue
或者break
被执行的语句。else
前面的except
条款不处理该条款中的例外情况。
如果finally
存在,则指定’清理’处理程序。该 try
子句被执行,包括任何except
和 else
条款。如果任何子句中发生异常但未处理,则会临时保存该异常。该finally
条款已执行。如果存在已保存的异常,则在该finally
子句的末尾重新引发 。如果该finally
子句引发另一个异常,则将保存的异常设置为新异常的上下文。如果finally
子句执行return
or break
语句,则丢弃已保存的异常:
>>>
>>> def f():
... try:
... 1/0
... finally:
... return 42
...
>>> f()
42
在执行该finally
子句期间,程序无法获得异常信息。
当一个return
,break
或continue
声明的执行try
套件中的try
… finally
语句时,finally
子句也“的出路。”执行 一个 continue
声明是非法finally
的条款。(原因是当前实施存在问题 – 将来可能会解除此限制)。
函数的返回值由return
执行的最后一个语句确定。由于finally
子句总是执行,因此在子句中执行的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_item(“,” with_item)*“:” with_item :: = [“as” ] suite expressiontarget
with
带有一个“项目” 的语句的执行过程如下:
-
with_item
评估上下文表达式(在其中给出的表达式)以获得上下文管理器。 -
__exit__()
加载上下文管理器以供以后使用。 -
__enter__()
调用上下文管理器的方法。 -
如果目标包含在
with
语句中,__enter__()
则会为其分配返回值。注意
该
with
语句保证如果__enter__()
方法返回时没有错误,则__exit__()
始终会被调用。因此,如果在分配给目标列表期间发生错误,则将其视为套件内发生的错误。见下面的第6步。 -
该套件已执行。
-
__exit__()
调用上下文管理器的方法。如果异常导致退出套件,则其类型,值和回溯将作为参数传递给__exit__()
。否则,提供三个None
参数。如果套件由于异常而退出,并且该
__exit__()
方法的返回值为 false,则重新引发该异常。如果返回值为true,则禁止异常,并继续执行语句后面的with
语句。如果套件由于异常以外的任何原因而退出,
__exit__()
则忽略返回值,并在正常位置继续执行所采用的退出类型。
如果有多个项目,则会处理上下文管理器,就好像with
嵌套了多个 语句一样:
with A() as a, B() as b:
suite
相当于
with A() as a:
with B() as b:
suite
在3.1版中更改:支持多个上下文表达式。
8.6。功能定义
函数定义定义用户定义的函数对象(请参阅 标准类型层次结构部分):
funcdef :: = [ decorators]“def” funcname“(”[ parameter_list]“)” [“ - >” expression]“:”
decorators :: = +
decorator :: =“@” [“(”[ [“,”]]“)”] NEWLINE
dotted_name :: = (“。” )*
parameter_list :: = (“,” )* [“,”[ ]]suite
decoratordotted_nameargument_listidentifieridentifierdefparameterdefparameterparameter_list_starargs
|parameter_list_starargs :: =“*”[ ](“,” )* [“,”[“**” [“,”]]]parameter_list_starargs
parameterdefparameterparameter
| “**”parameter[“,”]
参数 :: = identifier[“:” expression]
defparameter :: = parameter[“=” expression]
funcname :: = identifier
identifier
函数定义是可执行语句。它的执行将当前本地命名空间中的函数名绑定到一个函数对象(该函数的可执行代码的包装器)。此函数对象包含对当前全局命名空间的引用,作为调用函数时要使用的全局命名空间。
函数定义不执行函数体; 只有在调用函数时才会执行此操作。[2]
函数定义可以由一个或多个装饰器表达式包装。在包含函数定义的作用域中定义函数时,将评估Decorator表达式。结果必须是可调用的,以函数对象作为唯一参数调用。返回的值绑定到函数名称而不是函数对象。多个装饰器以嵌套方式应用。例如,以下代码
@f1(arg)
@f2
def func(): pass
大致相当于
def func(): pass
func = f1(arg)(f2(func))
除了原始函数没有临时绑定到名称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
”存在,则将其初始化为接收任何多余关键字参数的新有序映射,默认为相同类型的新空映射。“ *
”或“ *identifier
” 之后的参数是仅关键字参数,并且只能传递使用的关键字参数。
参数可以在参数名称后面具有“ ” 形式的注释。任何参数都可能有注释,甚至是表单或注释的注释 。函数可以在参数列表之后具有“返回”形式“ ”的注释。这些注释可以是任何有效的Python表达式。注释的存在不会改变函数的语义。注释值可用作由 函数对象的属性中的参数名称键入的字典的值。如果从中导入 :expression
*identifier
**identifier
-> expression
__annotations__
annotations
__future__
使用时,注释在运行时保留为字符串,从而可以推迟评估。否则,在执行函数定义时会对它们进行求值。在这种情况下,注释的评估顺序可能与它们在源代码中出现的顺序不同。
也可以创建匿名函数(未绑定到名称的函数),以便在表达式中立即使用。这使用Lambda表达式,如Lambdas一节中所述。请注意,lambda表达式只是简化函数定义的简写; 在“ def
”语句中定义的函数可以传递或分配给另一个名称,就像lambda表达式定义的函数一样。“ def
”形式实际上更强大,因为它允许执行多个语句和注释。
程序员注意:函数是一流的对象。def
在函数定义中执行的“ ”语句定义了可以返回或传递的本地函数。嵌套函数中使用的自由变量可以访问包含def的函数的局部变量。有关详细信息,请参阅命名和绑定一节 。
8.7。类定义
类定义定义了一个类对象(请参阅标准类型层次结构一节):
classdef :: = [ decorators]“class” classname[ inheritance]“:” inheritance :: =“(”[ ]“)” classname :: = suite argument_listidentifier
类定义是可执行语句。继承列表通常提供基类列表(请参阅更高级用法的元类),因此列表中的每个项目都应该计算为允许子类化的类对象。默认情况下,没有继承列表的类从基类继承object
; 因此,
class Foo:
pass
相当于
class Foo(object):
pass
然后使用新创建的本地命名空间和原始全局命名空间,在新的执行框架中执行类的套件(请参阅命名和绑定)。(通常,该套件主要包含函数定义。)当类的套件完成执行时,其执行框架将被丢弃,但其本地名称空间将被保存。[3]然后使用基类的继承列表和属性字典的已保存本地名称空间创建类对象。类名绑定到原始本地名称空间中的此类对象。
在类主体中定义属性的顺序保留在新类中__dict__
。请注意,这仅在创建类之后才可靠,并且仅适用于使用定义语法定义的类。
可以使用元类大量定制类创建。
类也可以装饰:就像装饰功能一样,
@f1(arg)
@f2
class Foo: pass
大致相当于
class Foo: pass
Foo = f1(arg)(f2(Foo))
装饰器表达式的评估规则与函数装饰器的评估规则相同。然后将结果绑定到类名。
程序员注意:类定义中定义的变量是类属性; 它们由实例共享。可以在方法中设置实例属性。类和实例属性都可以通过符号“ ” 访问,并且实例属性在以这种方式访问时会隐藏具有相同名称的类属性。类属性可以用作实例属性的默认值,但使用可变值可能会导致意外结果。 描述符 可用于创建具有不同实现细节的实例变量。self.name =value
self.name
8.8。协同程序
版本3.5中的新功能。
8.8.1。协程功能定义
async_funcdef :: = [ decorators]“async”“def” funcname“(” [ parameter_list]“)” [“ - >” expression]“:”suite
Python协同程序的执行可以在许多点暂停和恢复(参见协同程序)。协程函数体内部,await
并 async
标识成为保留关键字; await
表达式, 并且只能在协程函数体中使用。async for
async with
使用语法定义的函数始终是协程函数,即使它们不包含或关键字。async def
await
async
它是一个SyntaxError
在协程函数体内使用表达式。yield from
协程功能的一个例子:
async def func(param1, param2):
do_stuff()
await some_coroutine()
8.8.2。该声明async for
async_for_stmt :: =“async”
for_stmt
一个异步迭代能够调用其异步代码 ITER执行,并异步迭代器可以调用异步代码在它的下一个方法。
该语句允许对异步迭代器进行方便的迭代。async for
以下代码:
async for TARGET in ITER:
BLOCK
else:
BLOCK2
在语义上等同于:
iter = (ITER)
iter = type(iter).__aiter__(iter)
running = True
while running:
try:
TARGET = await type(iter).__anext__(iter)
except StopAsyncIteration:
running = False
else:
BLOCK
else:
BLOCK2
另见__aiter__()
和__anext__()
详细信息。
它是在协程函数体外SyntaxError
使用语句。async for
8.8.3。该声明async with
async_with_stmt :: =“async”
with_stmt
一个异步上下文管理器是一个上下文管理器,它能够暂停其执行进入和退出的方法。
以下代码:
async with EXPR as VAR:
BLOCK
在语义上等同于:
mgr = (EXPR)
aexit = type(mgr).__aexit__
aenter = type(mgr).__aenter__(mgr)
VAR = await aenter
try:
BLOCK
except:
if not await aexit(mgr, *sys.exc_info()):
raise
else:
await aexit(mgr, None, None, None)
另见__aenter__()
和__aexit__()
详细信息。
它是在协程函数体外SyntaxError
使用语句。async with
也可以看看
- PEP 492 – 具有异步和等待语法的协同程序
- 该协议使协同程序成为Python中适当的独立概念,并添加了支持语法。
脚注
[1] | 异常传播到调用堆栈,除非有一个finally 子句碰巧引发另一个异常。这个新的例外导致旧的例外丢失。 |
[2] | 作为函数体中第一个语句出现的字符串文字将转换为函数的__doc__ 属性,因此转换为函数的docstring。 |
[3] | 作为类体中第一个语句出现的字符串文字将转换为命名空间的__doc__ 项,因此转换为类的 docstring。 |