PEP 613 – 显式类型别名
- 作者:
- Shannon Zhu <szhu at fb.com>
- 发起人:
- Guido van Rossum <guido at python.org>
- 讨论至:
- Typing-SIG 讨论串
- 状态:
- 最终版
- 类型:
- 标准跟踪
- 主题:
- 类型标注
- 创建日期:
- 2020年1月21日
- Python 版本:
- 3.10
- 发布历史:
- 2020年1月21日
摘要
类型别名是用户指定的类型,它们可以像任何类型提示一样复杂,并通过模块顶层的简单变量赋值来指定。
本PEP将一种明确声明赋值为类型别名的方式正式化。
动机
类型别名被声明为顶层变量赋值。在PEP 484中,有效类型别名和全局变量之间的区别是隐式确定的:如果一个顶层赋值没有注解,并且赋值的值是一个有效类型,那么被赋值的名称就是一个有效的类型别名。否则,该名称只是一个不能用作类型提示的全局值。
这些隐式类型别名声明规则在类型别名涉及前向引用、无效类型或违反其他类型别名声明强制执行的限制时会造成混淆。由于未注解值和类型别名之间的区别是隐式的,因此模糊或不正确的类型别名声明会隐式默认为有效的赋值。这会创建无法表示为类型别名的表达式,并将格式错误的类型别名的错误诊断推迟到下游。
以下每个示例都包含一些由现有隐式别名声明导致的一些次优或令人困惑的行为的说明。为了便于比较,我们还在此处引入了TypeName: TypeAlias = Expression格式的显式别名,但语法将在后面的部分中详细讨论。
前向引用
MyType = "ClassName"
def foo() -> MyType: ...
只要ClassName稍后定义,此代码片段就不应出错。但是,类型检查器被迫假定MyType是一个赋值而不是类型别名,因此可能会抛出虚假错误,即 (1) MyType是一个未注解的全局字符串,以及 (2) MyType不能用作返回注解,因为它不是有效类型。
MyType: TypeAlias = "ClassName"
def foo() -> MyType: ...
显式别名消除了歧义,因此不会抛出上述任何错误。此外,如果ClassName存在问题(即,它实际上没有在后面定义),类型检查器可以抛出错误。
错误消息
MyType1 = InvalidType
MyType2 = MyGeneric(int) # i.e., intention was MyGeneric[int]
类型检查器应该在此代码片段上发出警告,指出InvalidType不是有效类型,因此不能用于注解表达式或构造类型别名。相反,类型检查器被迫抛出虚假错误,即 (1) MyType是一个缺少注解的全局表达式,以及 (2) MyType在整个代码库中MyType的所有用法中都不是有效类型。
MyType1: TypeAlias = InvalidType
MyType2: TypeAlias = MyGeneric(int)
通过显式别名,类型检查器有足够的信息来对坏类型别名的实际定义进行错误报告,并解释原因:MyGeneric(int)和InvalidType不是有效类型。当值表达式不再被评估为全局值时,可以抑制对整个代码库中MyType的所有用法上不可操作的类型错误。
作用域限制
class Foo:
x = ClassName
y: TypeAlias = ClassName
z: Type[ClassName] = ClassName
类型别名在类作用域内有效,无论是隐式的 (x) 还是显式的 (y)。如果该行应解释为类变量,则必须显式注解 (z)。
x = ClassName
def foo() -> None:
x = ClassName
外部的x是一个有效的类型别名,但如果内部的x被用作类型,类型检查器必须报错,因为类型别名不能在函数内部定义。这令人困惑,因为别名声明规则不明确,并且类型错误不会在内部类型别名声明的位置抛出,而是在其后续的每个用例中抛出。
x: TypeAlias = ClassName
def foo() -> None:
x = ClassName
def bar() -> None:
x: TypeAlias = ClassName
通过显式别名,外部赋值仍然是有效的类型变量。在foo内部,内部赋值应解释为x: Type[ClassName]。在bar内部,类型检查器应引发清晰的错误,告知作者类型别名不能在函数内部定义。
规范
显式别名声明语法清楚地区分了三种可能的赋值类型:类型化全局表达式、无类型全局表达式和类型别名。这避免了添加注解时破坏类型检查的赋值的存在,并避免了根据值的类型对赋值性质进行分类。
隐式语法(已存在)
x = 1 # untyped global expression
x: int = 1 # typed global expression
x = int # type alias
x: Type[int] = int # typed global expression
显式语法
x = 1 # untyped global expression
x: int = 1 # typed global expression
x = int # untyped global expression (see note below)
x: Type[int] = int # typed global expression
x: TypeAlias = int # type alias
x: TypeAlias = "MyClass" # type alias
注意:上面的示例单独说明了隐式和显式别名声明。为了向后兼容,类型检查器应同时支持两者,这意味着无类型全局表达式x = int仍将被视为有效的类型别名。
向后兼容性
显式别名提供了一种声明类型别名的替代方式,但所有现有代码和旧别名声明将像以前一样工作。
参考实现
Pyre类型检查器支持显式类型别名声明。
被拒绝的想法
考虑了一些用于显式别名的替代语法
MyType: TypeAlias[int]
这看起来很像一个未初始化的变量。
MyType = TypeAlias[int]
除了上面的选项,这种格式可能会在MyType的运行时值是什么方面增加混淆。
相比之下,所选择的语法选项MyType: TypeAlias = int很吸引人,因为它仍然坚持使用MyType = int赋值语法,并纯粹作为注解添加了一些信息给类型检查器。
版本历史
- 2021-11-16
- 允许在类作用域内使用
TypeAlias
- 允许在类作用域内使用
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0613.rst