7. 简单语句

简单语句由一个逻辑行构成。几个简单语句可以用分号分隔出现在单一的一行中。简单语句的语法是:

simple_stmt ::=  expression_stmt
                 | assert_stmt
                 | assignment_stmt
                 | augmented_assignment_stmt
                 | pass_stmt
                 | del_stmt
                 | return_stmt
                 | yield_stmt
                 | raise_stmt
                 | break_stmt
                 | continue_stmt
                 | import_stmt
                 | global_stmt
                 | nonlocal_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

(有关attributerefsubscriptionslicing的语法定义,请参见原语一节。)

赋值语句计算expression_list(记住,它可以是一个单一的表达式也可以是一个逗号分隔的序列,后者生成一个元组)并且从左到右把单一的结果对象赋值给target_list的每一个元素。

赋值是递归定义的,取决于目标(序列)的形式。当目标是可变对象的一部分时(属性引用,下标或者切片),最终必须由该可变对象做赋值操作并决定其合法性,如果赋值不可接受可以抛出一个异常。各种类型遵守的规则以及抛出的异常根据对象类型的定义给出(参见标准类型的层次结构一节)。

将对象分配给目标列表,可以括在圆括号或方括号中,递归地定义如下。

  • 如果目标列表为空:对象也必须是一个空的可迭代对象。
  • 如果目标列表位于括号中的单个目标:将对象分配给该目标。
  • 如果目标列表是以逗号分隔的目标列表,或者在方括号中的单个目标:对象必须是具有与目标列表中的目标相同数量的项目的一个可迭代对象,并且项目被从左向右赋值给相应的目标。
    • 如果目标列表包含一个带有星号(前缀为星号,称为“已加星标”的目标)的目标:对象必须是比目标列表中的目标项目数少一个的可迭代对象。可迭代对象的第一项从左到右赋值到已加星标的目标之前的目标。可迭代对象的最终项目分配给加星标的目标后的目标。然后将可迭代对象中剩余项目的列表分配给加星标的目标(列表可以为空)。
    • 否则:对象必须是与目标列表中的目标具有相同项目数量的可迭代对象,并且项目从左到右赋值给相应的目标。

赋值一个对象给一个单一的目标按如下方式递归定义。

  • 如果目标是一个标识符(名称):

    • 如果名称没有出现在当前代码块的globalnonlocal语句中:名称绑定到当前局部命名空间中的对象。
    • 否则:名称绑定到当前全局命名空间的对象或由nonlocal决定的外部命名空间。

    如果名称已经绑定,那么它将重新绑定。这可能导致之前绑定到该名称的对象的引用计数变为零,引起该对象被释放并调用它的析构函数(如果有的话)。

  • 如果目标是属性引用:计算引用中的初级表达式。它产生的对象应该具有一个可以赋值的属性;如果情况不是这样,则抛出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()创建的属性。

  • 如果目标是下标操作符:计算引用中的初级表达式。它应该生成一个可变的序列对象(例如列表)或者映射对象(例如字典)。然后,计算下标表达式。

    如果primary是一个可变的序列对象(例如一个列表),则下标必须产生一个普通的整数。如果它是负数,将会加上序列的长度。结果值必须是一个小于序列长度的非负整数,然后将要求序列赋值该对象给具有那个索引的元素。如果索引超出范围,则引发IndexError异常(给序列下标赋值不能添加新的元素到序列中)。

    如果primary 是一个映射对象(例如一个字典),下标必须具有和映射的关键字类型兼容的类型,然后要求映射创建一个键/值对将下标映射到赋值的对象。这既可以替换一个具有相同键值得已存在键/值对,也可以插入一个新的键/值对(如果不存在相同的键)。

    对于用户定义的对象,使用适当的参数调用__ 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                     ::=  "+=" | "-=" | "*=" | "@=" | "/=" | "//=" | "%=" | "**="
                               | ">>=" | "<<=" | "&=" | "^=" | "|="

(最后三个符号的语法定义参见原语一节。)

增强的赋值将先求值target(和普通的赋值语句不同,它不可以是一个可拆分的对象)和expression_list,然后完成针对两个操作数的二元操作,最后将结果赋值给初始的target。target只计算一次。

x += 1这样增强的赋值表达式可以重写成x = x + 1以达到类似但不完全等同的效果。在增强版本中,x只计算一次。还有,如果可能,真实的操作是原地的,意思是不创建一个新的对象并赋值给target,而是直接修改旧的对象。

与正常赋值不同,增强的赋值在计算右侧之前计算左侧。例如,a[i] += f(x)首先查找a [i ],然后求值f(x)并执行加法,最后,将结果写回到a[i]

除了不可以在一个语句中赋值给元组和多个目标,增强的赋值语句完成的赋值和普通的赋值以相同的方式处理。类似地,除了可能出现的原地行为,增强的赋值完成的二元操作和普通的二元操作相同。

如果target是属性引用,关于类和实例属性的注意事项同样适用于正常的赋值。

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在编译时刻,当要求优化时,目前的代码生成器不会为断言语句生成任何代码。注:不必把失败的表达式的源代码包含进错误信息;它将作为栈回溯的一部分显示出来。

__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在语法上只可以出现在函数定义中,不可以出现在类定义中。

如果存在expression_list,则计算它,否则使用None替换。

return离开当前的函数调用时以expression_list(或None)作为返回值。

return将控制传出带有finally子句的try语句时,在真正离开函数之前会执行finally子句。

在生成器函数中,return语句表示生成器已经完成并将引发StopIteration返回值(如果有)用作构造StopIteration的参数,并成为StopIteration.value属性。

7.7. yield 语句

yield_stmt ::=  yield_expression

yield语句在语义上等同于yield表达式yield语句可以用来省略在等效的yield expression语句中需要的括号。例如,yield语句

yield <expr>
yield from <expr>

等价于yield expression语句

(yield <expr>)
(yield from <expr>)

yield语句只在定义生成器函数时使用,且只在生成器函数的函数体中使用。在函数定义中使用yield语句就足以创建一个生成器函数而不是普通函数。

有关yield语义的详细信息,请参阅Yield表达式部分。

7.8. raise语句

raise_stmt ::=  "raise" [expression ["from" expression]]

如果没有表达式,raise 重新抛出当前作用域中最后一个激活的异常。如果当前作用域中没有活动的异常,则抛出TypeError异常以表示这是一个错误。

否则,raise会将第一个表达式计算为异常对象。它必须是BaseException的子类或实例。如果它是一个类,当需要时,将通过实例化没有参数的类来获得异常实例。

异常的类型是异常实例的类,是实例本身。

通常在引发异常时将traceback对象自动创建,并将其作为可写的__ traceback __属性附加到该对象。你可以使用with_traceback()异常方法(它返回相同的异常实例,其traceback设置为其参数)创建异常并设置自己的追溯,如下所示:

raise Exception("foo occurred").with_traceback(tracebackobj)

from子句用于异常链接:如果给定,第二个expression必须是另一个异常类或实例,然后它将作为引发的异常的__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>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

如果在异常处理程序或finally子句中引发异常,则类似的机制隐式地工作:将先前的异常作为新异常的__context__属性:

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

有关异常的其他信息,请参见异常部分,有关处理异常的信息,请参见try语句

7.9. break语句

break_stmt ::=  "break"

break只能在语法上嵌套在forwhile循环中,但不能嵌套在该循环中的函数或类定义中。

它终止最近的外围循环,如果循环有一个可选的else子句,则跳过它。

如果break终止了一个for循环,控制循环的目标保持当前的值。

break将控制传出带有finally子句的try语句时,在离开循环之前会执行finally子句。

7.10. continue语句

continue_stmt ::=  "continue"

continue在语法上只可以出现在forwhile循环中,但不能嵌套在这些循环内的函数定义、类定义和finally子句中。它继续最内层循环的下一轮。

continue将控制传出带有finally子句的try语句时,在真正开始下一轮循环之前会执行finally子句。

7.11. import语句

import_stmt     ::=  "import" module ["as" name] ( "," module ["as" name] )*
                     | "from" relative_module "import" identifier ["as" name]
                     ( "," identifier ["as" name] )*
                     | "from" relative_module "import" "(" identifier ["as" name]
                     ( "," identifier ["as" name] )* [","] ")"
                     | "from" module "import" "*"
module          ::=  (identifier ".")* identifier
relative_module ::=  "."* module | "."+
name            ::=  identifier

基本的导入语句(无from子句)分两步执行:

  1. 找到一个模块,如果需要,加载和初始化它
  2. 定义import语句所在作用域的局部命名空间中的一个或多个名称。

当语句包含多个子句(以逗号分隔)时,为每个子句分别执行这两个步骤,就像子句已被分隔为单独的import语句一样。

第一步,查找和加载模块的细节在导入系统部分有更详细的描述,它还描述了可以导入的各种类型的包和模块,以及所有可用于定制导入系统的钩子。请注意,此步骤中的失败可能表示模块无法找到表示在初始化模块时发生错误,包括执行模块代码。

如果请求的模块被成功检索,它将通过以下三种方式之一在本地命名空间中可用:

  • 如果模块名称后面是as,则as之后的名称将直接绑定到导入的模块。
  • 如果未指定其他名称,并且正在导入的模块是顶级模块,则模块的名称将在本地命名空间中绑定为对导入模块的引用
  • 如果正在导入的模块不是顶级模块,则包含该模块的顶级包的名称在本地命名空间中被绑定为对顶级包的引用。导入的模块必须使用其完全限定名称而不是直接访问

from形式使用稍微更复杂的过程:

  1. 找到from子句中指定的模块,如果需要,加载和初始化它;
  2. 对于import子句中指定的每个标识符:
    1. 检查导入的模块是否具有该名称的属性
    2. 如果没有,请尝试导入具有该名称的子模块,然后再次检查导入的模块的该属性
    3. 如果未找到该属性,则引发ImportError
    4. 否则,对该值的引用存储在本地命名空间中,使用as子句中的名称(如果存在),否则使用属性名称

例子:

import foo                 # foo imported and bound locally
import foo.bar.baz         # foo.bar.baz imported, foo bound locally
import foo.bar.baz as fbb  # foo.bar.baz imported and bound as fbb
from foo.bar import baz    # foo.bar.baz imported and 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相对导入的规范包含在 PEP 328中。

importlib.import_module()用于支持动态确定要加载的模块的应用程序。

7.11.1. Future语句

future语句是一个针对编译器的指令,它指示某个特定的模块应该使用在未来版本的Python中可用的语法或者语义来编译。

future语句旨在简化未来向引入不兼容的更改的Python版本的迁移。它允许在该功能变成标准之前以每个模块为基础使用新的功能。

future_statement ::=  "from" "__future__" "import" feature ["as" name]
                      ("," feature ["as" name])*
                      | "from" "__future__" "import" "(" feature ["as" name]
                      ("," feature ["as" name])* [","] ")"
feature          ::=  identifier
name             ::=  identifier

future语句必须出现在模块顶部附近。在future语句之前可以出现的行只有:

  • 模块的文档字符串(如果有的话),
  • 注释,
  • 空白行,和
  • 其它future语句。

Python 3.0识别的功能有absolute_importdivisiongeneratorsunicode_literalsprint_function nested_scopeswith_statement它们都是冗余的,因为它们始终启用,并且只保留用于向后兼容性。

future语句的识别和特殊处理在编译的时刻:核心语义的变化通常通过生成不同的代码实现。甚至有可能新的功能引入新的不兼容语法(例如一个新的保留字),在这种情况下编译器可能需要以不同的方式解析模块。这些决定不可能推迟到运行时刻。

对于任何给定的版本,编译器知道已经定义了哪些特征名称,并且如果一个future语句包含其未知的特征,则引发编译时错误。

直接运行时语义与任何导入语句相同:有一个标准模块__future__,它将在执行future语句时以通常的方式导入,稍后描述。

运行时的语义取决于feature语句所启用的特定功能。

注意下面的语句没有什么特别的:

import __future__ [as name]

它不是future 语句;它是一个普通的没有特殊语义和语法限制的import 语句。

在包含future语句的模块M中,调用内建函数exec()compile()编译的代码默认将使用与future语句关联的新的语法或语义。这种行为可以通过compile()可选的参数可以控制 — 详细信息参见该函数的文档。

在交互式解释器提示符处键入的future语句将对解释器会话的其余部分生效。如果解释器以-i 选项启动,然后传入一个脚本的名称给它执行,且脚本包含一个future 语句,它将在脚本执行之后的交互式会话中生效。

请参阅

PEP 236 —— 回到__future__
关于__future__机制的原始提案。

7.12. global语句

global_stmt ::=  "global" identifier ("," identifier)*

global语句是针对当前整个代码块的声明。这意味着列在其后的所有标识符将被解析为全局有效的。不用global给全局变量赋值是不可能实现的,尽管自由变量可以引用全局变量而不用声明为全局的。

在相同的代码块中,global语句中列出的名称不可以在global语句之前使用。

global语句列出的名称不可定义为形式参数或者定义在for循环的控制目标、定义、函数定义或import语句中。

CPython实现细节: 目前的实现没有强制实行后两个限制,但是程序不应该滥用这个自由,因为未来的实现可能强制实行或者默默地改变程序的含义。

程序员注意:global是针对解析器的指令。它只应用于与global语句同一时刻解析的代码。特别地,内建函数exec()中包含的global语句不会影响包含这个函数调用的代码块,包含这个函数调用的代码中的global语句不影响这个字符串中包含的代码。这同样适用于eval()compile()函数。

7.13. nonlocal语句

nonlocal_stmt ::=  "nonlocal" identifier ("," identifier)*

nonlocal语句使得列出的标识符引用之前在最近的外围作用域中绑定的变量,全局变量除外。这很重要,因为绑定的默认行为是首先搜索本地命名空间。该语句允许封装的代码重新绑定局部作用域外面的变量,除全局(模块)作用域之外。

global语句中列出的名称不同,nonlocal语句中列出的名称必须引用外围作用域中的预先存在的绑定(创建新绑定的作用域不能明确地确定)。

nonlocal语句中列出的名称不得与局部作用域中的预先存在的绑定冲突。

请参阅

PEP 3104 - 访问外部作用域中的名称
nonlocal语句的规范。