PEP 561 – 分发和打包类型信息
- 作者:
- Ethan Smith <ethan at ethanhs.me>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 主题:
- 打包, 类型提示
- 创建:
- 2017年9月9日
- Python 版本:
- 3.7
- 历史记录:
- 2017年9月10日,2017年9月12日,2017年10月6日,2017年10月26日,2018年4月12日
摘要
PEP 484 将类型提示引入 Python,其目标是使类型提示逐渐且易于采用。目前,类型信息必须手动分发。此 PEP 提供了一种标准化的方法,可以利用现有的工具来打包和分发类型信息,并最少的工作量,以及类型检查器解析模块并收集这些信息以进行类型检查的顺序。
基本原理
目前,包作者希望分发包含内联类型信息的代码。此外,维护者希望分发存根文件以保持 Python 2 兼容性,同时使用更新的注释语法。但是,没有标准方法来分发包含类型信息的包。此外,如果希望私下分发存根文件,则唯一可用的方法是通过设置 MYPYPATH
或等效项来手动指向存根。如果该包可以公开发布,则可以将其添加到 typeshed [1]。但是,这无法扩展,并且会给 typeshed 的维护者带来负担。此外,它将存根中的错误修复与使用 typeshed 的工具的版本绑定。
PEP 484 有一个关于分发类型信息的简短章节。在此 章节 中,PEP 建议使用 shared/typehints/pythonX.Y/
来分发存根文件。但是,为每个第三方库手动添加存根文件的路径无法扩展。人们采用的最简单的方法是将其 site-packages
添加到他们的 MYPYPATH
中,但这会导致类型检查器在高度动态的包(例如 sqlalchemy 和 Django)上失败。
术语定义
“MAY”、“MUST”和“SHOULD”以及“SHOULD NOT”的定义应按 RFC 2119 中所述进行解释。
“内联” - 类型是使用 PEP 526 和 PEP 3107 语法(文件名以 .py
结尾)作为运行时代码的一部分。
“存根” - 仅包含类型信息的文件,不包含运行时代码(文件名以 .pyi
结尾)。
“分发” 是用于发布和分发版本的打包文件。(PEP 426)
“模块” 包含 Python 运行时代码或存根类型信息的文件。
“包” 是命名 Python 模块的目录或目录。(请注意包和分发之间的区别。虽然大多数分发都以它们安装的一个包命名,但有些分发会安装多个包。)
规范
在包中支持类型提示有几种动机和方法。此 PEP 识别用户希望创建的三种类型的包
- 包维护者希望内联添加类型信息。
- 包维护者希望通过存根添加类型信息。
- 第三方或包维护者希望共享包的存根文件,但维护者不想将其包含在包的源代码中。
此 PEP 的目标是支持所有三种场景,并使它们易于添加到打包和部署中。
此规范的两个主要部分是打包规范和解析模块类型信息的解析顺序。类型检查规范旨在替换 shared/typehints/pythonX.Y/
PEP 484 的规范。
新的第三方存根库 SHOULD 通过此 PEP 中提出的第三方打包方法分发存根,而不是添加到 typeshed 中。Typeshed 将继续使用,但如果找到维护者,typeshed 中的第三方存根 MAY 被拆分为它们自己的包。
打包类型信息
为了使打包和分发类型信息尽可能简单易用,打包和分发是通过现有框架完成的。
希望支持其代码类型检查的包维护者 MUST 将名为 py.typed
的标记文件添加到支持类型提示的包中。此标记递归应用:如果顶级包包含它,则其所有子包 MUST 也支持类型检查。要将此文件与包一起安装,维护者可以使用现有的打包选项,例如 distutils 中的 package_data
,如下所示。
Distutils 选项示例
setup(
...,
package_data = {
'foopkg': ['py.typed'],
},
...,
)
对于命名空间包(请参阅 PEP 420),py.typed
文件应位于命名空间的子模块中,以避免冲突并提高清晰度。
此 PEP 不支持将类型信息作为模块级分发或命名空间包中的单个文件模块的一部分进行分发。
单个文件模块应重构为包,并指示该包如上所述支持类型提示。
仅存根包
对于希望分发包含其所有类型信息的存根文件的包维护者,最好将 *.pyi
存根放在相应的 *.py
文件旁边。但是,存根也可以放在单独的包中并单独分发。如果希望分发存根文件,第三方也可以发现此方法很有用。存根包的名称 MUST 遵循 foopkg-stubs
的方案,用于名为 foopkg
的包的类型存根。请注意,对于仅存根包,不需要添加 py.typed
标记,因为名称 *-stubs
足以表明它是类型信息来源。
寻求分发存根文件的第三方鼓励与包的维护者联系,了解有关与包一起分发的信息。如果维护者不希望维护或打包存根文件或内联类型信息,则可以创建一个第三方仅存根包。
此外,仅存根分发 SHOULD 指示运行时包支持的版本,方法是通过正常的依赖项数据指示运行时分发的版本。例如,存根包 flyingcircus-stubs
可以通过 distutils 基于工具中的 install_requires
或其他打包工具中的等效项来指示其支持的运行时 flyingcircus
分发的版本。请注意,在 pip 9.0 中,如果更新 flyingcircus-stubs
,它将更新 flyingcircus
。在 pip 9.0 中,可以使用 --upgrade-strategy=only-if-needed
标志。在 pip 10.0 中,这是默认行为。
对于命名空间包(请参阅 PEP 420),仅存根包应仅在根命名空间包上使用 -stubs
后缀。所有仅存根命名空间包都应省略 __init__.pyi
文件。py.typed
标记文件对于仅存根包不是必需的,但与包含内联类型的包类似,如果使用,它们应位于命名空间的子模块中,以避免冲突并提高清晰度。
例如,如果 pentagon
和 hexagon
是安装在命名空间包 shapes.polygons
中的单独分发,则相应的仅类型分发应生成如下布局的包
shapes-stubs
└── polygons
└── pentagon
└── __init__.pyi
shapes-stubs
└── polygons
└── hexagon
└── __init__.pyi
类型检查器模块解析顺序
以下是支持此 PEP 的类型检查器 SHOULD 解析包含类型信息的模块的顺序
- 手动放在路径开头的存根或 Python 源代码。类型检查器 SHOULD 提供此功能,以允许用户完全控制要使用哪些存根,以及修补包中损坏的存根/内联类型。在 mypy 中,可以使用
$MYPYPATH
环境变量来实现此目的。 - 用户代码 - 类型检查器正在运行的文件。
- 存根包 - 这些包 SHOULD 优先于任何已安装的内联包。对于包
foopkg
,它们可以在foopkg-stubs
中找到。 - 带有
py.typed
标记文件的包 - 如果没有覆盖已安装的包,并且它选择参与类型检查,则 SHOULD 使用与包捆绑在一起的类型(无论它们是在.pyi
类型存根文件中还是内联在.py
文件中)。 - Typeshed(如果使用) - 提供标准库类型和一些第三方库。
如果类型检查器在步骤 3 中识别出没有所需模块的仅存根命名空间包,则它们应继续执行步骤 4/5。类型检查器应通过 __init__.pyi
的不存在来识别命名空间包。这允许不同的子包独立选择内联与仅存根。
检查与运行版本不同的 Python 版本的类型检查器**必须**在该 Python 版本的 site-packages
/dist-packages
中找到类型信息。这可以通过例如 pythonX.Y -c 'import site; print(site.getsitepackages())'
来查询。还建议类型检查器允许用户指向特定的 Python 二进制文件,以防它不在路径中。
部分存根包
许多存根包只完成了库类型接口的一部分,尤其是在最初阶段。为了便于类型检查和代码编辑器,包可以是“部分的”。这意味着在上述模块解析顺序的第四部分和第五部分(即内联包和 typeshed)中**应该**搜索存根包中未找到的模块。
类型检查器应该合并存根包和运行时包或 typeshed 目录。这可以被认为等同于将存根包复制到与相应的运行时包或 typeshed 文件夹相同的目录中,并对组合的目录结构进行类型检查。因此,类型检查器**必须**保持正常的解析顺序,即在 *.py
文件之前检查 *.pyi
文件。
如果存根包分发是部分的,它**必须**在 py.typed
文件中包含 partial\n
。对于在命名空间包中分发的存根包(请参阅 PEP 420),py.typed
文件应该位于命名空间的子模块中。
类型检查器应该将存根包中的命名空间包视为不完整的,因为多个分发版可能会填充它们。存根包分发版中命名空间包内的常规包被认为是完整的,除非包含带有 partial\n
的 py.typed
。
实现
指示对类型支持的提议方案完全向后兼容,并且不需要修改包工具。包含内联类型的示例包可在 [typed_package] 中找到,以及 [stub_package]。一个示例包检查器 [pkg_checker],它读取已安装包的元数据并报告其状态,例如未类型化、内联类型化或存根包。
mypy 类型检查器实现了 PEP 561 搜索,您可以在 mypy 文档 [4] 中了解相关信息。
[numpy-stubs] 是 numpy 分发版的一个真实仅存根包的示例。
致谢
如果没有 Ivan Levkivskyi、Jelle Zijlstra、Alyssa Coghlan、Daniel F Moisset、Andrey Vlasovskikh、Nathaniel Smith 和 Guido van Rossum 的想法、反馈和支持,这个 PEP 将是不可能的。
版本历史
- 2023-01-13
- 阐明 模块解析顺序 的第 4 步适用于任何带有
py.typed
标记文件的包(而不仅仅是内联包)。
- 阐明 模块解析顺序 的第 4 步适用于任何带有
- 2021-09-20
- 阐明仅存根命名空间包的期望和类型检查器行为。
- 阐明命名空间包中单个文件模块的处理方式。
- 2018-07-09
- 添加指向仅存根包示例的链接。
- 2018-06-19
- 部分存根包可以查看 typeshed 以及运行时包。
- 2018-05-15
- 添加部分存根包规范。
- 2018-04-09
- 添加对 mypy 实现的引用。
- 阐明存根包的优先级。
- 2018-02-02
- 将仅存根包的后缀更改为 -stubs,而不是 _stubs。
- 注意,仅存根包不需要 py.typed。
- 添加关于 pip 和升级存根包的说明。
- 2017-11-12
- 重写为仅使用现有工具。
- 无需在元数据中指示类型信息的种类。
- 标记文件名称从
.typeinfo
更改为py.typed
。
- 2017-11-10
- 规范重写为使用包元数据而不是分发元数据。
- 删除了仅存根包,并将其合并到第三方包规范中。
- 删除了类型检查器考虑检查运行时版本的建议。
- 更新了实现以反映 PEP 更改。
- 2017-10-26
- 添加了实现参考。
- 添加了致谢和版本历史记录。
- 2017-10-06
- 重写为使用 .distinfo/METADATA 而不是特定于 distutils 的命令。
- 阐明第三方存根包的版本控制。
- 2017-09-11
- 添加了有关当前解决方案和 typeshed 的信息。
- 阐明基本原理。
参考文献
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0561.rst
上次修改时间:2024-06-05 15:44:49 GMT