Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python增强提案

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是历史文档:请参阅 类型别名typing.TypeAlias 以获取最新的规范和文档。规范的类型规范在 类型规范站点 上维护;运行时类型行为在CPython文档中进行了描述。

×

请参阅 类型规范更新流程,了解如何提出对类型规范的更改。

摘要

类型别名是用户指定的类型,其复杂度可以与任何类型提示一样,并且通过模块顶层的简单变量赋值来指定。

此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

来源: https://github.com/python/peps/blob/main/peps/pep-0613.rst

上次修改: 2024-06-11 22:12:09 GMT