PEP 849:更具表现力的类型表达式

PEP 849:更具表现力的类型表达式

💡 原文英文,约3100词,阅读约需12分钟。
📝

内容提要

该PEP提议扩展Python类型注解以支持任意表达式,新增AST注解格式,使注解函数返回AST和命名空间供运行时求值。同时新增typing.get_type_annotations等工具,弃用get_type_hints,以提升注解表达力,性能影响主要限于运行时求值用户。

🔎

延伸解读

为何需要新的注解格式

当前Python类型注解只能使用有限表达式,因为运行时需要内省。像1 | 2 | 3这样的写法会被解释为按位或,结果变成3,无法表达字面量联合类型。PEP 849通过让注解函数返回AST和命名空间,使任意表达式都能在运行时被求值为类型对象,从而支持更丰富的类型语法。

对性能的实际影响

根据参考实现,不使用注解的模块导入时间无显著变化;使用注解的模块导入反而略快,内存占用也略小。但运行时求值注解时,最坏情况下耗时可能增加约七倍。不过,当名称未定义而需使用STRING或FORWARDREF格式时,现有方法也较慢,与新方案相当。总体影响主要限于运行时求值用户。

迁移与兼容性注意

typing.get_type_hints将被弃用,建议迁移到annotationlib.get_annotations或新的typing.get_type_annotations。新格式仅增加功能,无向后兼容问题。未来新语义只对当前无效的注解语法生效,不会改变现有有效注解的含义。库作者应使用提供的工具函数来支持新语法。

使用限制与变通方法

新类型表达式只能在注解中使用,像cast(<复杂表达式>, value)这样的地方无法直接支持,因为解释器无法区分。变通方法是先定义类型别名:type _TargetType = <复杂表达式>,然后cast(_TargetType, value)。类型别名也基于注解函数实现,因此能使用新语法。

❓

Q&A

PEP 849 提议对 Python 类型注解做什么核心改变?

PEP 849 提议扩展注解函数,使任意 Python 表达式都能用于类型注解。它引入一种新的注解格式,让注解函数返回注解的 AST 和命名空间,供运行时消费者求值成类型对象。

为什么 Python 目前不能把 1 | 2 | 3 当作 Literal[1, 2, 3] 使用?

因为 1 | 2 | 3 会被解释为按位或运算,结果只是 3,而不是字面量类型的联合。此外常量折叠会从字节码中消除该表达式,导致运行时无法获取原始类型注解。

PEP 849 中新的 AST 注解格式具体如何工作?

注解函数被调用时传入 Format.AST,会返回 AnnotationAST 对象,其中包含注解的 ast.expr 和用于求值的命名空间。编译器生成的注解函数会把 AST 数据存为字符串常量,每次调用时构造新的 AnnotationAST。

typing.get_type_annotations 和现有的 get_type_hints 有什么区别?

get_type_annotations 会以 Format.AST 调用底层注解函数,然后用 typing.evaluate_type_ast 构造类型对象,支持新的类型特性。而 get_type_hints 语义略有不同,无法修改以支持新功能,因此将被弃用。

PEP 849 对性能有什么影响?

对不使用类型注解的模块,导入时间无显著差异;对使用注解的模块,导入略快且内存占用略小。但运行时求值可能显著变慢,最坏情况下(值格式且所有名称已定义)约增加七倍。不过负面影响只影响最小的用户群。

在 cast 等非注解位置使用复杂类型表达式时,PEP 849 建议怎么处理?

建议使用中间类型别名,例如 type _TargetType = <some complex expression>,然后 cast(_TargetType, value)。因为类型别名也通过注解函数实现,这样可以避免解释器无法区分普通函数调用的问题。

🏷️

标签

➡️

继续阅读