7. 简单语句¶
简单语句包含于单个逻辑行之内。 多个简单语句可出现于由分号分隔的单个逻辑行中。 简单语句的句法是:
simple_stmt:expression_stmt
|assert_stmt
|assignment_stmt
|augmented_assignment_stmt
|annotated_assignment_stmt
|pass_stmt
|del_stmt
|return_stmt
|yield_stmt
|raise_stmt
|break_stmt
|continue_stmt
|import_stmt
|future_stmt
|global_stmt
|nonlocal_stmt
|type_stmt
7.1. 表达式语句¶
表达式语句被用于(主要是交互式地)计算和写入一个值,或者(通常地)调用一个过程 (一个没有返回有意义结果的函数;在 Python 中,过程的返回值为 None
)。 表达式语句的其他用法也是允许且有用的。 表达式语句的句法是:
expression_stmt: starred_expression
一个表达式语句会对表达式列表(也可能只是单个表达式)进行求值。
在交互模式下,如果值不是 None
,它会通过内置的 repr()
函数被转换为字符串,其结果字符串将单独在一行被写入到标准输出(除非结果是 None
,这样就不会有过程调用的输出了)。
7.2. 赋值语句¶
赋值语句用于将名称(重)绑定到值,以及修改可变对象的属性或成员项:
assignment_stmt: (target_list
"=")+ (starred_expression
|yield_expression
) target_list:target
(","target
)* [","] target:identifier
| "(" [target_list
] ")" | "[" [target_list
] "]" |attributeref
|subscription
|slicing
| "*"target
(attributeref、subscription 和 slicing 的句法定义请参阅 原语 一节。)
赋值语句会对表达式列表(注意这可以为单个表达式或以逗号分隔的列表,后者会生成一个元组)求值,并将单个结果对象从左至右逐个赋给目标列表。
赋值是根据目标(列表)的形式递归定义的。 当目标是可变对象(属性引用、订阅或切片)的一部分时,该可变对象必须最终执行赋值并决定其有效性,如果赋值操作不可接受则可能引发异常。 各种类型所遵循的规则以及引发的异常在对象类型的定义中给出(参见 标准类型层级结构)。
将一个对象赋值给一个目标列表,任选地以圆括号或方括号括起,其递归定义如下。
如果目标列表是单个目标且没有末尾逗号,任选地在圆括号内,则对象会被赋给该目标。
否则:
如果目标列表包含一个带星号前缀的目标,称为“加星”目标:则对象必须是一个可迭代对象,其拥有的项数至少要比目标列表中的目标数少一。 该可迭代对象的第一部分项会从左至右被赋给加星目标之前的目标。 该可迭代对象的最后一部分项会被赋给加星目标之后的目标。 然后该可迭代对象中剩余项的列表会被赋给加星目标(该列表可以为空)。
否则:对象必须是具有与目标列表中的目标数量相同的项的可迭代对象,并且这些项会从左到右分配给相应的目标。
将一个对象赋给单个目标的的递归定义如下。
如果目标是标识符(名称):
如果名称已经被绑定,它将被重新绑定。 这可能导致之前被绑定到该名称的对象的引用计数变为零,导致该对象被释放,其析构器(如果存在)被调用。
如果目标是一个属性引用:则引用中的主表达式会被求值。 它应该产生一个具有可赋值属性的对象;如果不是这种情况,则会引发
TypeError
。 然后该对象会被要求将赋值对象赋给给定的属性;如果它无法执行赋值,则会引发一个异常(通常但不一定是AttributeError
)。注意:如果对象是类实例并且属性引用在赋值运算符的两侧都出现,则右侧表达式
a.x
可以访问实例属性,或者(如果不存在实例属性)类属性。 左侧目标a.x
总是被设置为实例属性,必要时会创建它。 因此,a.x
的两次出现不一定指向相同的属性:如果右侧表达式指向一个类属性,则左侧会创建一个新的实例属性作为赋值的目标。class Cls: x = 3 # class variable inst = Cls() inst.x = inst.x + 1 # writes inst.x as 4 leaving Cls.x as 3
此描述不一定适用于描述器属性,例如用
property()
创建的特性。如果目标是一个下标引用:则该引用中的主表达式会被求值。 它应该产生一个可变序列对象(例如列表)或一个映射对象(例如字典)。 接下来,会对下标表达式求值。
如果主对象是一个可变序列对象(例如列表),下标必须产生一个整数。 如果为负数,则会加上序列的长度。 结果值必须是小于序列长度的非负整数,并且序列会被要求将赋值对象赋给该索引位置的项。 如果索引超出范围,则会引发
IndexError
(对下标序列的赋值不能向列表中添加新项)。如果主对象是一个映射对象(例如字典),下标必须是与该映射的键类型兼容的类型,然后该映射会被要求创建一个键/值对,将下标映射到所赋的对象。 这可以替换具有相同键值的现有键/值对,或插入一个新的键/值对(如果没有具有相同值的键存在)。
对于用户定义的对象,将使用适当的参数调用
__setitem__()
方法。如果目标是一个切片:则引用中的主表达式会被求值。 它应该产生一个可变序列对象(例如列表)。 被赋值的对象应该是一个相同类型的序列对象。 接下来,如果存在下界和上界表达式,则会对它们求值;默认值为零和序列的长度。 边界值应为整数。 如果任一边界值为负数,则会加上序列的长度。 最终的边界值将被裁剪以位于零和序列长度(含)之间。 最后,序列对象会被要求用被赋值序列的项替换该切片。 切片的长度可能与被赋值序列的长度不同,从而在目标序列允许的情况下改变目标序列的长度。
CPython 实现细节:在当前实现中,目标的语法与表达式的语法相同,并且无效的语法在代码生成阶段被拒绝,导致错误消息不够详细。
虽然赋值的定义意味着左侧和右侧之间的重叠是“同时”的(例如,a, b = b, a
交换两个变量),但在被赋值变量的集合*内部*的重叠是从左到右发生的,有时会导致混淆。 例如,以下程序打印 [0, 2]
:
x = [0, 1]
i = 0
i, x[i] = 1, 2 # i is updated, then x[i] is updated
print(x)
参见
- PEP 3132 - 扩展的可迭代对象解包
*target
功能的规范。
7.2.1. 增强赋值语句¶
增强赋值是将二元运算和赋值语句组合在单个语句中的写法:
augmented_assignment_stmt:augtarget
augop
(expression_list
|yield_expression
) augtarget:identifier
|attributeref
|subscription
|slicing
augop: "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**=" | ">>=" | "<<=" | "&=" | "^=" | "|="
(关于最后三个符号的句法定义,请参阅 原语 一节。)
增强赋值会对目标(与普通赋值语句不同,它不能是解包)和表达式列表进行求值,对两个操作数执行特定于赋值类型的二元运算,并将结果赋给原始目标。 目标只被求值一次。
一个像 x += 1
这样的增强赋值语句可以重写为 x = x + 1
,以达到类似但不完全相同的效果。在增强版本中,x
只被求值一次。此外,在可能的情况下,实际操作是*就地*执行的,这意味着不是创建一个新对象并将其赋给目标,而是修改旧对象。
与普通赋值不同,增强赋值在求值右侧*之前*先求值左侧。例如,a[i] += f(x)
首先查找 a[i]
,然后求值 f(x)
并执行加法,最后将结果写回 a[i]
。
除了赋给元组和在单个语句中赋给多个目标外,增强赋值语句所做的赋值处理方式与普通赋值相同。同样,除了可能的*就地*行为外,增强赋值执行的二元运算与普通的二元运算相同。
对于作为属性引用的目标,关于类和实例属性的注意事项同样适用于常规赋值。
7.2.2. 带注解的赋值语句¶
注解赋值是在单个语句中组合变量或属性注解和可选赋值语句的写法:
annotated_assignment_stmt:augtarget
":"expression
["=" (starred_expression
|yield_expression
)]
与普通赋值语句的区别在于,只允许单个目标。
如果赋值目标由一个未括在括号中的单个名称组成,则该目标被视为“简单”目标。对于简单赋值目标,如果在类或模块作用域内,注解会被收集到一个延迟求值的注解作用域中。可以使用类或模块的 __annotations__
属性,或使用 annotationlib
模块中的工具来对注解进行求值。
如果赋值目标不是简单的(属性、下标节点或带括号的名称),则注解永远不会被求值。
如果在函数作用域内对名称进行注解,则该名称对于该作用域是局部的。注解永远不会在函数作用域内被求值和存储。
如果右侧存在,带注解的赋值会执行实际的赋值,就好像没有注解存在一样。如果对于表达式目标右侧不存在,则解释器会求值目标,除了最后的 __setitem__()
或 __setattr__()
调用。
参见
在 3.8 版更改: 现在,带注解的赋值允许在右侧使用与常规赋值相同的表达式。以前,一些表达式(如未加括号的元组表达式)会导致语法错误。
在 3.14 版更改: 注解现在在一个单独的 注解作用域 中延迟求值。如果赋值目标不是简单的,注解将永远不会被求值。
7.3. assert
语句¶
Assert 语句是一种向程序中插入调试断言的简便方法:
assert_stmt: "assert"expression
[","expression
]
简单形式 assert expression
等价于
if __debug__:
if not expression: raise AssertionError
扩展形式 assert expression1, expression2
等价于
if __debug__:
if not expression1: raise AssertionError(expression2)
这些等价性假设 __debug__
和 AssertionError
指的是具有这些名称的内置变量。在当前实现中,内置变量 __debug__
在正常情况下为 True
,当请求优化时(命令行选项 -O
)为 False
。当前代码生成器在编译时请求优化时不会为 assert
语句生成任何代码。请注意,不必在错误消息中包含失败表达式的源代码;它将作为堆栈跟踪的一部分显示。
对 __debug__
的赋值是非法的。内置变量的值在解释器启动时确定。
7.4. pass
语句¶
pass_stmt: "pass"
pass
是一个空操作——当它被执行时,什么也不会发生。当语法上需要一个语句但不需要执行任何代码时,它可用作占位符,例如:
def f(arg): pass # a function that does nothing (yet)
class C: pass # a class with no methods (yet)
7.5. del
语句¶
del_stmt: "del" target_list
删除的递归定义与赋值的定义非常相似。这里不详述,只提供一些提示。
删除目标列表会从左到右递归地删除每个目标。
删除名称会从本地或全局命名空间中移除该名称的绑定,具体取决于该名称是否出现在同一代码块的 global
语句中。尝试删除未绑定的名称会引发 NameError
异常。
删除属性引用、订阅和切片会被传递给相关的主对象;删除切片通常等同于赋一个正确类型的空切片(但即使这也由被切片的对象决定)。
在 3.2 版更改: 以前,如果一个名称在嵌套块中作为自由变量出现,那么从本地命名空间中删除该名称是非法的。
7.6. return
语句¶
return_stmt: "return" [expression_list
]
return
只能在语法上嵌套在函数定义中,不能嵌套在类定义中。
如果存在表达式列表,则对其求值,否则用 None
替代。
return
会带着表达式列表(或 None
)作为返回值离开当前的函数调用。
当 return
将控制权传出带有 finally
子句的 try
语句时,该 finally
子句会在真正离开函数之前执行。
在生成器函数中,return
语句表示生成器已完成,并将导致引发 StopIteration
。返回值(如果有)用作构造 StopIteration
的参数,并成为 StopIteration.value
属性。
在异步生成器函数中,空的 return
语句表示异步生成器已完成,并将导致引发 StopAsyncIteration
。在异步生成器函数中,非空的 return
语句是语法错误。
7.7. yield
语句¶
yield_stmt: yield_expression
yield
语句在语义上等同于 yield 表达式。yield
语句可用于省略在等效的 yield 表达式语句中所需的括号。例如,yield 语句
yield <expr>
yield from <expr>
等价于 yield 表达式语句
(yield <expr>)
(yield from <expr>)
Yield 表达式和语句仅在定义生成器函数时使用,并且仅在生成器函数的主体中使用。在函数定义中使用 yield
足以使该定义创建生成器函数而不是普通函数。
7.8. raise
语句¶
raise_stmt: "raise" [expression
["from"expression
]]
如果不存在表达式,raise
会重新引发当前正在处理的异常,也称为*活动异常*。如果当前没有活动异常,则会引发 RuntimeError
异常,表示这是一个错误。
否则,raise
会将第一个表达式求值为异常对象。它必须是 BaseException
的子类或实例。如果它是一个类,则在需要时通过无参数实例化该类来获取异常实例。
异常的类型是异常实例的类,值是实例本身。
通常在引发异常时会自动创建一个回溯对象,并将其作为 __traceback__
属性附加到异常上。你可以使用 with_traceback()
异常方法一步创建异常并设置自己的回溯(该方法返回同一个异常实例,其回溯设置为其参数),如下所示:
raise Exception("foo occurred").with_traceback(tracebackobj)
from
子句用于异常链:如果给定,第二个*表达式*必须是另一个异常类或实例。如果第二个表达式是异常实例,它将作为 __cause__
属性(可写)附加到引发的异常上。如果表达式是异常类,该类将被实例化,生成的异常实例将作为 __cause__
属性附加到引发的异常上。如果引发的异常未被处理,两个异常都将被打印出来:
>>> try:
... print(1 / 0)
... except Exception as exc:
... raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("Something bad happened") from exc
RuntimeError: Something bad happened
当已经处理一个异常时引发新异常,类似的机制会隐式工作。当使用 except
或 finally
子句,或者 with
语句时,可以处理异常。前一个异常然后作为新异常的 __context__
属性附加:
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
print(1 / 0)
~~^~~
ZeroDivisionError: division by zero
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
raise RuntimeError("Something bad happened")
RuntimeError: Something bad happened
可以通过在 from
子句中指定 None
来显式抑制异常链:
>>> try:
... print(1 / 0)
... except:
... raise RuntimeError("Something bad happened") from None
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened
有关异常的更多信息可以在异常部分找到,有关处理异常的信息在try 语句部分。
在 3.3 版更改: None
现在被允许作为 raise X from Y
中的 Y
。
增加了 __suppress_context__
属性以抑制异常上下文的自动显示。
在 3.11 版更改: 如果在 except
子句中修改了活动异常的回溯,后续的 raise
语句会用修改后的回溯重新引发该异常。以前,异常会用捕获时的回溯重新引发。
7.9. break
语句¶
break_stmt: "break"
break
只能在语法上嵌套在 for
或 while
循环中,但不能嵌套在该循环内的函数或类定义中。
它会终止最近的封闭循环,如果循环有可选的 else
子句,则跳过该子句。
如果 for
循环被 break
终止,循环控制目标会保持其当前值。
当 break
将控制权传出带有 finally
子句的 try
语句时,该 finally
子句在真正离开循环之前执行。
7.10. continue
语句¶
continue_stmt: "continue"
continue
只能在语法上嵌套在 for
或 while
循环中,但不能嵌套在该循环内的函数或类定义中。它会继续执行最近的封闭循环的下一次迭代。
当 continue
将控制权传出带有 finally
子句的 try
语句时,该 finally
子句在真正开始下一次循环迭代之前执行。
7.11. import
语句¶
import_stmt: "import"module
["as"identifier
] (","module
["as"identifier
])* | "from"relative_module
"import"identifier
["as"identifier
] (","identifier
["as"identifier
])* | "from"relative_module
"import" "("identifier
["as"identifier
] (","identifier
["as"identifier
])* [","] ")" | "from"relative_module
"import" "*" module: (identifier
".")*identifier
relative_module: "."*module
| "."+
基本的导入语句(没有 from
子句)分两步执行:
查找模块,必要时加载并初始化它
在
import
语句发生的范围的本地命名空间中定义一个或多个名称。
当语句包含多个子句(用逗号分隔)时,这两个步骤会为每个子句分别执行,就像子句被分成单独的导入语句一样。
第一步,查找和加载模块的详细信息在导入系统一节中有更详细的描述,该节还描述了可以导入的各种包和模块类型,以及所有可用于自定义导入系统的钩子。请注意,此步骤中的失败可能表示模块无法找到,*或者*在初始化模块时发生错误,其中包括执行模块的代码。
如果成功检索到请求的模块,它将通过以下三种方式之一在本地命名空间中可用:
如果模块名称后跟
as
,则as
后面的名称将直接绑定到导入的模块。如果没有指定其他名称,并且要导入的模块是顶级模块,则模块的名称将作为对导入模块的引用绑定在本地命名空间中。
如果要导入的模块*不是*顶级模块,则包含该模块的顶级包的名称将作为对顶级包的引用绑定在本地命名空间中。必须使用其完全限定名称访问导入的模块,而不是直接访问。
from
形式使用一个稍微复杂的过程:
查找
from
子句中指定的模块,必要时加载并初始化它;对于
import
子句中指定的每个标识符:检查导入的模块是否具有该名称的属性
如果没有,尝试导入一个具有该名称的子模块,然后再次检查导入的模块是否具有该属性
如果未找到该属性,则引发
ImportError
。否则,对该值的引用将存储在本地命名空间中,如果存在
as
子句,则使用其中的名称,否则使用属性名称。
示例:
import foo # foo imported and bound locally
import foo.bar.baz # foo, foo.bar, and foo.bar.baz imported, foo bound locally
import foo.bar.baz as fbb # foo, foo.bar, and foo.bar.baz imported, foo.bar.baz bound as fbb
from foo.bar import baz # foo, foo.bar, and foo.bar.baz imported, foo.bar.baz bound as baz
from foo import attr # foo imported and foo.attr bound as attr
如果标识符列表被星号('*'
)替换,则模块中定义的所有公共名称都将绑定在 import
语句发生的范围的本地命名空间中。
模块定义的*公共名称*通过检查模块的命名空间中名为 __all__
的变量来确定;如果定义了,它必须是一个字符串序列,这些字符串是该模块定义或导入的名称。__all__
中给出的名称都被视为公共的,并且必须存在。如果未定义 __all__
,则公共名称集合包括在模块命名空间中找到的所有不以下划线字符('_'
)开头的名称。__all__
应该包含整个公共 API。它旨在避免意外导出不属于 API 的项(例如在模块内导入和使用的库模块)。
通配符形式的导入 — from module import *
— 仅在模块级别允许。尝试在类或函数定义中使用它将引发 SyntaxError
。
在指定要导入哪个模块时,您不必指定模块的绝对名称。当模块或包包含在另一个包中时,可以在同一个顶级包内进行相对导入,而无需提及包名。通过在 from
后面的指定模块或包中使用前导点,您可以指定在不指定确切名称的情况下向上遍历当前包层次结构的层级。一个前导点表示执行导入的模块所在的当前包。两个点表示向上一级包。三个点是向上两级,依此类推。因此,如果您从 pkg
包中的一个模块执行 from . import mod
,那么您最终将导入 pkg.mod
。如果您从 pkg.subpkg1
内执行 from ..subpkg2 import mod
,您将导入 pkg.subpkg2.mod
。相对导入的规范包含在包相对导入部分。
importlib.import_module()
用于支持动态确定要加载的模块的应用程序。
引发一个审计事件 import
,参数为 module
、filename
、sys.path
、sys.meta_path
、sys.path_hooks
。
7.11.1. future 语句¶
future 语句是给编译器的指令,指示特定模块应使用在未来指定 Python 版本中将可用的语法或语义进行编译,届时该功能将成为标准。
future 语句旨在简化向引入不兼容语言更改的未来 Python 版本的迁移。它允许在功能成为标准之前,在每个模块的基础上使用新功能。
future_stmt: "from" "__future__" "import"feature
["as"identifier
] (","feature
["as"identifier
])* | "from" "__future__" "import" "("feature
["as"identifier
] (","feature
["as"identifier
])* [","] ")" feature:identifier
future 语句必须出现在模块的顶部附近。在 future 语句之前只能出现以下内容:
模块文档字符串(如果有),
注释,
空行,和
其他 future 语句。
唯一需要使用 future 语句的功能是 annotations
(参见 PEP 563)。
Python 3 仍然识别所有由 future 语句启用的历史功能。该列表包括 absolute_import
、division
、generators
、generator_stop
、unicode_literals
、print_function
、nested_scopes
和 with_statement
。它们都是多余的,因为它们总是被启用,仅为了向后兼容而保留。
future 语句在编译时被识别并特殊处理:核心构造语义的更改通常通过生成不同的代码来实现。甚至可能出现新功能引入新的不兼容语法(例如新的保留字),在这种情况下,编译器可能需要以不同的方式解析模块。这些决定不能推迟到运行时。
对于任何给定的版本,编译器都知道定义了哪些功能名称,如果 future 语句包含它不知道的功能,则会引发编译时错误。
直接的运行时语义与任何导入语句相同:有一个标准模块 __future__
,稍后描述,它将在 future 语句执行时以通常的方式导入。
有趣的运行时语义取决于 future 语句启用的特定功能。
请注意,以下语句没有什么特别之处:
import __future__ [as name]
这不是 future 语句;这是一个普通的导入语句,没有特殊的语义或语法限制。
在包含 future 语句的模块 M
中,通过调用内置函数 exec()
和 compile()
编译的代码,默认将使用与 future 语句相关的新语法或语义。这可以通过 compile()
的可选参数来控制——有关详细信息,请参阅该函数的文档。
在交互式解释器提示符下键入的 future 语句将在解释器会话的其余部分生效。如果解释器以 -i
选项启动,并传递一个要执行的脚本名称,并且该脚本包含 future 语句,则该语句将在脚本执行后启动的交互式会话中生效。
参见
- PEP 236 - 回到 __future__
__future__ 机制的原始提案。
7.12. global
语句¶
global_stmt: "global"identifier
(","identifier
)*
global
语句使列出的标识符被解释为全局变量。没有 global
就不可能给全局变量赋值,尽管自由变量可以在未声明为全局的情况下引用全局变量。
global
语句适用于整个当前作用域(模块、函数体或类定义)。如果在作用域中,变量在其全局声明之前被使用或赋值,则会引发 SyntaxError
。
在模块级别,所有变量都是全局的,因此 global
语句没有效果。但是,变量仍必须在其 global
声明之前不被使用或赋值。此要求在交互式提示符(REPL)中被放宽。
程序员注意:global
是对解析器的指令。它仅适用于与 global
语句同时解析的代码。特别是,包含在提供给内置 exec()
函数的字符串或代码对象中的 global
语句不会影响*包含*该函数调用的代码块,并且此类字符串中包含的代码不受包含该函数调用的代码中的 global
语句的影响。同样适用于 eval()
和 compile()
函数。
7.13. nonlocal
语句¶
nonlocal_stmt: "nonlocal"identifier
(","identifier
)*
当函数或类的定义嵌套(封闭)在其他函数的定义中时,其非局部作用域是封闭函数的局部作用域。nonlocal
语句使列出的标识符引用先前在非局部作用域中绑定的名称。它允许封装的代码重新绑定此类非局部标识符。如果一个名称在多个非局部作用域中被绑定,则使用最近的绑定。如果一个名称在任何非局部作用域中都未被绑定,或者没有非局部作用域,则会引发 SyntaxError
。
nonlocal
语句适用于函数或类体的整个作用域。如果在作用域中,变量在其非局部声明之前被使用或赋值,则会引发 SyntaxError
。
7.14. type
语句¶
type_stmt: 'type'identifier
[type_params
] "="expression
type
语句声明一个类型别名,它是 typing.TypeAliasType
的实例。
例如,以下语句创建一个类型别名:
type Point = tuple[float, float]
这段代码大致等同于:
annotation-def VALUE_OF_Point():
return tuple[float, float]
Point = typing.TypeAliasType("Point", VALUE_OF_Point())
annotation-def
表示一个 注解作用域,其行为大多像一个函数,但有一些细微的差异。
类型别名的值在注解作用域中求值。它在创建类型别名时不会被求值,而只有在通过类型别名的 __value__
属性访问值时才会被求值(参见 延迟求值)。这允许类型别名引用尚未定义的名称。
通过在名称后添加类型形参列表,可以使类型别名成为泛型。更多信息请参见泛型类型别名。
type
是一个软关键字。
3.12 新版功能.
参见
- PEP 695 - 类型形参语法
引入了
type
语句以及泛型类和函数的语法。