Python - PEP 749 – PEP 649 的实现补充

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

Python - PEP 749 – PEP 649 的实现补充

摘要

本 PEP 补充了 PEP 649 的规范,具体包括:

  • from __future__ import annotationsPEP 563)将至少保留到 Python 3.13 生命周期结束,之后会弃用并最终移除。
  • 新增标准库模块 annotationlib,为注解提供工具,包括 get_annotations()、注解格式枚举、ForwardRef 类及辅助的 __annotate__ 调用函数。
  • REPL 环境下的注解与模块级注解一样延迟求值。
  • 明确诸如 classmethod()functools.wraps() 等包装器提供注解时的行为。
  • 不再用代码标志位区分“可在伪 globals 环境执行的 annotate”,而是新增 VALUE_WITH_FAKE_GLOBALS 格式供第三方实现表明支持的格式。
  • 直接删除 __annotations__ 属性时也会清除 __annotate__
  • 支持 PEP 695、PEP 696 增加的类型别名值、类型参数约束/默认值等用类似 PEP 649 的延迟求值方式。
  • SOURCE 格式更名为 STRING,提升清晰度。
  • 条件定义的类和模块注解能正确处理。
  • 若在部分执行的模块访问注解,返回已执行部分的注解,但不缓存。

动机

PEP 649 为 Python 注解提供了更优雅的延迟求值机制,极大提升了类型提示和运行时类型系统的可用性、优雅性和能力。PEP 649 最初计划在 3.10 实现,2023 年被正式接受,但实现延期至 3.14。

在实现过程中发现 PEP 649 部分细节未明确定义,个别角落行为也有值得改进之处。本 PEP 不替代而是补充 PEP 649,对部分细节加以完善,提升整体用户体验。

from __future__ import annotations 的未来

PEP 563 引入 future import annotations,让所有注解都变为字符串。PEP 649 提出无需 future import 的新方案,并计划弃用 563,但未具体说明弃用步骤。

规范

建议的弃用计划如下:

  • Python 3.14 及以前,from __future__ import annotations 行为不变,注解全为字符串。
    • 若激活该 future import,带注解对象的 __annotate__ 返回字符串形式(VALUE 格式),与 __annotations__ 保持一致。
  • 在不支持 PEP 649 的最后一个 Python 版本(预计为 3.13)停止维护后,开始弃用该 future import。使用时编译器发出 DeprecationWarning。弃用最早从 3.13 EOL 后第一个大版本开始,也可延后。
  • 至少两个大版本后,彻底移除该 future import,注解始终按 PEP 649 延迟求值。继续使用则抛出 SyntaxError

被拒绝的方案

  • 立即让 future import 失效:会破坏那些依赖 forwardref 且 eager 求值的代码,影响面广,不可行。
  • 立即弃用 future import:许多库依赖该特性实现前向引用,若立即弃用无法兼容所有 Python 版本且避免警告,不现实。
  • 永久保留 future import:会导致 Python 语义永远分裂为两种模式,不利于语言一致性。
  • 未来让 future import 变为无操作:不如明确要求用户移除 future import,避免歧义。

新增 annotationlib 模块

PEP 649 建议把注解相关工具加入 inspect,但该模块过大且依赖众多,性能不佳。我们决定新建 annotationlib,专门提供注解工具,也便于后续扩展。

原理

PEP 649 建议用 typing.ForwardRef 实现 FORWARDREF 格式,但现有 typing.ForwardRef 与 typing 模块耦合,且对外文档不足但被三方库大量依赖。我们将其替换为 annotationlib.ForwardRef,并兼容原有用法。保留 _evaluate 方法但标记为弃用,同时在 typing 新增 evaluate_forward_ref,专门用于类型提示下的前向引用求值。

新增 annotationlib.call_annotate_function 辅助调用 __annotate__,便于如 typing.NamedTuple 之类在类构造过程中部分求值注解。

规范

标准库新增 annotationlib

  • get_annotations():获取函数、模块、类注解,将取代 inspect.get_annotations()(后者将委托前者,暂不弃用)。
  • get_annotate_from_class_namespace(namespace):从类命名空间获取 __annotate__
  • Format:注解格式枚举,含 VALUE、VALUE_WITH_FAKE_GLOBALS、FORWARDREF、STRING(原 PEP 649 的 VALUE、FORWARDREF、SOURCE)。
  • ForwardRef:前向引用类,含 __forward_arg__evaluate() 等。
  • call_annotate_function(func, format):辅助调用 __annotate__,若不支持给定格式则自动用 fake globals 环境。
  • call_evaluate_function(func, format):类似上,但用于 PEP 695/696 的延迟属性求值。
  • annotations_to_string(annotations):将注解 dict 全部转为字符串形式。
  • type_repr(value):单个对象转字符串,类型用完全限定名,其他用 repr()。

typing 模块新增 evaluate_forward_ref,递归求值前向引用,特化于类型提示。

PEP 649 的 VALUE、FORWARDREF、SOURCE 等常量不会作为 inspect 全局成员,只能通过 annotationlib.Format.VALUE 等访问。

被拒绝的方案

  • 其他名称(如 annotation、annotools、annotationslib、annotationlib 等),最终选择了 annotationlib
  • 加入 inspect/typing/types 模块,理由同上,不便于隔离注解与类型提示、类型本身。
  • 作为三方包开发,标准库自身已需用这些功能,直接内置更好。
  • 先做成私有模块,后公开。实际标准库与三方很快都会用,直接设计正式 API 更合适。

REPL 行为

PEP 649 原计划 REPL 下注解立即求值,与模块行为不同。但这样实现更复杂且会让用户疑惑。我们建议 REPL 下也延迟求值,与模块一致。

每一行含注解的全局代码会生成新的 __annotate__,前一行的 __annotate__ 会被覆盖。REPL 没有全局命名空间的 __annotations__ 键。

类和函数定义同样延迟注解求值,annotationlib 可用于 introspect。

包装器对象的注解

标准库包装器(如 functools.wrapsclassmethodstaticmethod)应:

  • 尽可能延迟求值,保持一致性和兼容性。
  • functools.update_wrapper/wraps 只复制 __annotate____annotations__ 用复制的 __annotate__
  • classmethod/staticmethod 构造器提供可写的 __annotate____annotations__,读取时从被包裹对象取并缓存,写入时只改自身,不影响被包裹对象。

元类与注解

发现 metaclass 与 class 注解交互存在老 bug,比如 metaclass 的注解会意外影响子类。PEP 649 延迟求值后,__annotate____annotations__ 只在特定条件下设置,依赖 type 描述符,若 metaclass 字典有同名键会导致描述符失效,出现异常。

最终方案:在类字典存储 __annotate__/__annotations__ 时用内部专用名,避免与 type 描述符冲突。用户不应直接访问类字典获取注解,应用 annotationlib.get_annotate_from_class_namespace

被拒绝的方案

  • 总是/从不在类字典插入注解相关键,或用特殊描述符,都有实现/兼容性/复杂性权衡。最终选择内部隐藏存储。

新增 VALUE_WITH_FAKE_GLOBALS 格式

PEP 649 原建议用 code flag 指示 __annotate__ 是否能在 fake globals 环境运行。我们改为新增枚举值 VALUE_WITH_FAKE_GLOBALS,编译器生成的 annotate 函数支持该格式,第三方手写的不支持时应抛 NotImplementedError

规范

annotationlib.Format 增加 VALUE_WITH_FAKE_GLOBALS,值为 2。其余格式依次递增。函数如不支持该格式应抛 NotImplementedError

删除 annotations 的效果

PEP 649 规定设置 __annotations__ 会同步置 __annotate__ 为 None,但未说明删除时如何处理。本 PEP 补充:删除 __annotations__ 时也应将 __annotate__ 设为 None。

PEP 695/696 对象的延迟求值

PEP 695/696 新增的类型别名值、类型参数约束/默认值等已用延迟求值,但未暴露底层函数。现新增:

  • typing.TypeAliasType.evaluate_value
  • typing.TypeVar.evaluate_boundevaluate_constraintsevaluate_default
  • typing.ParamSpec.evaluate_default
  • typing.TypeVarTuple.evaluate_default

属性为只读,未设置时为 None。可配合 annotationlib.call_evaluate_function 求值。

dataclass 字段类型行为

延迟求值后,dataclass 字段类型可用前向引用,但 field.type 属性保留 ForwardRef,不自动求值。文档应指导用户用 ForwardRef.evaluate 显式求值。未来可视需求增加自动求值方法。

SOURCE 更名为 STRING

原 SOURCE 格式旨在还原源码,但实际上只能生成字符串,且不总能还原原始源代码,故更名为 STRING。现在支持四种格式:

  • VALUE
  • VALUE_WITH_FAKE_GLOBALS
  • FORWARDREF
  • STRING

条件定义的注解

PEP 649 原不支持类/模块体内条件定义注解,但实际用得较多(如 SQLAlchemy)。本 PEP 采用唯一标识记录已执行注解,__annotate__ 只返回执行过的注解。

部分执行模块的注解缓存

PEP 649 原实现部分执行模块时返回空注解且缓存,现规定:此时返回已执行的注解但不缓存,后续访问会重新调用 __annotate__,包含新增注解。

实现细节

  • ForwardRef 只实现少量方法,FORWARDREF 格式下实际用内部 stringizer,最终转换为 ForwardRef 对象。
  • __annotate__ 函数的参数名 format 为仅限位置参数,避免注解中符号冲突。

向后兼容性

PEP 649 已详述所有代码的兼容性影响。本 PEP 主要补充新代码与旧 introspection 工具的兼容问题,如装饰器等需升级支持 FORWARDREF 格式。标准库相关工具已更新以兼容本 PEP 规范。

安全影响

PEP 649 使得访问注解可能执行任意代码(包括 STRING 格式),但历史上访问注解后续操作(如 isinstance、typing 工具、repr)本就可能执行任意代码,且只有已导入代码才会触发。

教学建议

PEP 649 及本 PEP 的延迟注解语义对普通用户直观友好,无需手动加引号。需要 introspect 的高级用户可参考 annotationlib 文档。

参考实现

本 PEP 变更已在 CPython 主分支实现。

致谢

感谢 Larry Hastings 撰写 PEP 649,Carl Meyer、Alex Waygood、Alyssa Coghlan、David Ellis、Nikita Sobolev 等在本 PEP 制定和标准库实现中的贡献。

附录:哪些表达式能被 stringizer 还原

PEP 649 承认 stringizer 不能还原所有表达式。可还原的 AST 节点包括:

  • BinOp
  • UnaryOp(~+- 支持,not 不支持)
  • Dict(除 ** 展开)
  • Set
  • Compare(==!=<<=>>= 支持,但部分顺序可能变化,isin 等不支持)
  • Call(除 ** 展开)
  • Constant(但转义等信息丢失)
  • Attribute(前提不是常量)
  • Subscript(前提不是常量)
  • Starred
  • Name
  • List
  • Tuple
  • Slice

不支持或报错:

  • FormattedValue、JoinedStr(f-string)
  • BoolOp(and、or)、IfExp、Lambda、推导式、生成器表达式(结果错误)

注解作用域禁止 NamedExpr、Await、Yield 等。

版权

本文档可置于公有领域或 CC0-1.0-Universal 许可下,以更宽松者为准。

原文地址:https://peps.python.org/pep-0749/

您可能感兴趣的与本文相关的镜像

Python3.8

Python3.8

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

csdddn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值