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 的语法定义,请参见 Primaries 部分。)
赋值语句计算表达式列表(请记住,这可以是一个表达式或以逗号分隔的列表,后者产生一个元组),并将单个结果对象从左到右分配给每个目标列表。
赋值的定义是根据目标(列表)的形式递归定义的。当目标是可变对象(属性引用、下标或切片)的一部分时,可变对象必须最终执行赋值并决定其有效性,如果赋值不可接受,则可能会引发异常。 各种类型观察到的规则和引发的异常在对象类型的定义中给出(请参见 标准类型层次结构 部分)。
将对象赋值给目标列表(可以选择用括号或方括号括起来)的定义是递归定义的,如下所示。
如果目标列表是单个目标,没有尾随逗号,可以选择用括号括起来,则将对象分配给该目标。
否则
如果目标列表包含一个以星号为前缀的目标,称为“星号”目标: 该对象必须是可迭代对象,其项目数至少与目标列表中的目标数(减 1)相同。 可迭代对象的第一个项目从左到右分配给星号目标之前的目标。 可迭代对象的最后一个项目被分配给星号目标之后的目标。 然后将可迭代对象中剩余项目的列表分配给星号目标(该列表可以为空)。
否则:该对象必须是可迭代对象,其项目数与目标列表中的目标数相同,并且项目从左到右分配给相应的目标。
将对象赋值给单个目标的定义是递归定义的,如下所示。
如果目标是标识符(名称)
否则:该名称分别绑定到全局命名空间或由
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()
创建的属性。如果目标是下标:则计算引用中的主表达式。 它应该产生可变序列对象(例如列表)或映射对象(例如字典)。 接下来,计算下标表达式。
如果主对象是可变序列对象(例如列表),则下标必须产生一个整数。 如果为负数,则将其与序列的长度相加。 结果值必须是一个小于序列长度的非负整数,并要求序列将其项分配给具有该索引的已分配对象。 如果索引超出范围,则会引发
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__
中,该属性是一个字典,将变量名(如果私有则会被改写)映射到已评估的注解。如果静态地找到注解,则此属性是可写的,并且在类或模块主体执行开始时会自动创建。
如果赋值目标不是简单的(属性、下标节点或带括号的名称),则如果在类或模块作用域中,则会评估注解,但不会存储。
如果在函数作用域中注解了名称,则该名称是该作用域的本地名称。注解永远不会在函数作用域中被评估和存储。
如果右侧存在,则带注解的赋值在评估注解之前执行实际赋值(如果适用)。如果表达式目标的右侧不存在,则解释器将评估目标,除了最后一次 __setitem__()
或 __setattr__()
调用。
另请参阅
在 3.8 版本中更改: 现在,带注解的赋值允许右侧使用与普通赋值相同的表达式。以前,某些表达式(如未加括号的元组表达式)会导致语法错误。
7.3. 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
子句,则会跳过该子句。
如果通过 break
终止了 for
循环,则循环控制目标将保留其当前值。
当 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
| "."+
基本的 import 语句(没有 from
子句)分两步执行
查找模块,并在必要时加载和初始化它
在
import
语句出现的范围的局部命名空间中定义一个或多个名称。
当语句包含多个子句(以逗号分隔)时,将分别对每个子句执行这两个步骤,就像将子句分隔为单独的 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()
来支持动态确定要加载的模块的应用程序。
使用参数 module
、filename
、sys.path
、sys.meta_path
、sys.path_hooks
引发 审计事件 import
。
7.11.1. 未来语句¶
未来语句是指示编译器应使用将在指定的 Python 未来版本中可用的语法或语义来编译特定模块的指令,其中该功能将成为标准。
未来语句旨在简化向 Python 未来版本的迁移,这些版本会引入与语言不兼容的更改。它允许在功能成为标准版本之前在每个模块的基础上使用新功能。
future_stmt ::= "from" "__future__" "import"feature
["as"identifier
] (","feature
["as"identifier
])* | "from" "__future__" "import" "("feature
["as"identifier
] (","feature
["as"identifier
])* [","] ")" feature ::=identifier
未来语句必须出现在模块的顶部附近。在未来语句之前出现的行只能是
模块文档字符串(如果有)、
注释、
空行和
其他未来语句。
唯一需要使用未来语句的功能是 annotations
(请参阅 PEP 563)。
Python 3 仍然可以识别未来语句启用的所有历史功能。该列表包括 absolute_import
、division
、generators
、generator_stop
、unicode_literals
、print_function
、nested_scopes
和 with_statement
。它们都是冗余的,因为它们始终启用,并且仅为了向后兼容而保留。
未来语句在编译时被识别并进行特殊处理:核心构造的语义更改通常通过生成不同的代码来实现。甚至可能出现新功能引入新的不兼容语法(例如新的保留字)的情况,在这种情况下,编译器可能需要以不同的方式解析模块。此类决策不能推迟到运行时。
对于任何给定的版本,编译器都知道已定义哪些功能名称,并且如果未来语句包含它不知道的功能,则会引发编译时错误。
直接的运行时语义与任何 import 语句的语义相同:有一个标准模块 __future__
,稍后描述,它将在执行未来语句时以通常的方式导入。
有趣的运行时语义取决于未来语句启用的特定功能。
请注意,语句没有什么特别之处
import __future__ [as name]
这不是未来语句;它是一个普通的 import 语句,没有特殊的语义或语法限制。
在包含未来语句的模块 M
中,通过调用内置函数 exec()
和 compile()
编译的代码,默认情况下将使用与未来语句关联的新语法或语义。这可以通过 compile()
的可选参数来控制——有关详细信息,请参阅该函数的文档。
在交互式解释器提示符处键入的未来语句将对解释器会话的其余部分生效。如果使用 -i
选项启动解释器,传递一个要执行的脚本名称,并且该脚本包含未来语句,则它将在脚本执行后启动的交互式会话中生效。
另请参阅
- PEP 236 - 回到 __future__
__future__ 机制的原始提案。
7.12. global
语句¶
global_stmt ::= "global"identifier
(","identifier
)*
global
语句使列出的标识符被解释为全局变量。如果没有 global
,将无法为全局变量赋值,尽管自由变量可以引用全局变量,而无需声明为全局变量。
global
语句适用于函数或类主体的整个范围。如果在作用域中全局声明之前使用或赋值变量,则会引发 SyntaxError
。
程序员注意事项: global
是一个给解析器的指令。它仅适用于与 global
语句同时解析的代码。特别地,一个包含在字符串或传递给内置 exec()
函数的代码对象中的 global
语句不会影响包含函数调用的代码块,并且包含在这样的字符串中的代码不受包含函数调用的代码中的 global
语句的影响。同样的情况适用于 eval()
和 compile()
函数。
7.13. nonlocal
语句¶
nonlocal_stmt ::= "nonlocal"identifier
(","identifier
)*
当函数或类的定义嵌套(封闭)在其他函数的定义中时,它的 nonlocal 作用域是封闭函数的局部作用域。nonlocal
语句使列出的标识符引用先前在 nonlocal 作用域中绑定的名称。它允许封装的代码重新绑定此类 nonlocal 标识符。如果一个名称绑定在多个 nonlocal 作用域中,则使用最近的绑定。如果一个名称没有绑定在任何 nonlocal 作用域中,或者如果不存在 nonlocal 作用域,则会引发 SyntaxError
异常。
nonlocal
语句适用于函数或类体的整个作用域。如果在作用域中在其 nonlocal 声明之前使用或赋值给变量,则会引发 SyntaxError
异常。
程序员注意事项: nonlocal
是给解析器的指令,并且仅适用于与其一起解析的代码。请参阅 global
语句的说明。
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
语句以及泛型类和函数的语法。