Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 396 – 模块版本号

作者:
Barry Warsaw <barry at python.org>
状态:
已拒绝
类型:
信息
主题:
打包
创建:
2011-03-16
发布历史:
2011-04-05

目录

摘要

鉴于为 Python 模块指定版本号既有用又常见,并且鉴于在 Python 社区中,这种做法已经自然而然地发展出不同的方式,因此有必要为模块作者建立一些标准约定,供他们遵守和参考。这份信息性 PEP 描述了想要定义其 Python 模块版本号的 Python 模块作者的最佳实践。

遵守本 PEP 是可选的,但是其他 Python 工具(例如 distutils2 [1])可能会根据这里定义的约定进行调整。

PEP 拒绝

本 PEP 于 2021-04-14 正式被拒绝。自从该 PEP 首次编写以来,打包生态系统发生了重大变化,并且诸如 importlib.metadata.version() [11] 之类的 API 提供了更好的体验。

用户故事

爱丽丝正在编写一个名为 alice 的新模块,她想与其他 Python 开发人员分享它。 alice 是一个简单的模块,位于一个文件中,即 alice.py。爱丽丝想指定一个版本号,以便她的用户可以知道他们使用的是哪个版本。由于她的模块完全在一个文件中,她想将版本号添加到该文件中。

鲍勃编写了一个名为 bob 的模块,他已与许多用户分享了该模块。 bob.py 包含一个版本号,以便用户方便使用。鲍勃了解到 Cheeseshop [2],并添加了一些简单的打包操作(使用经典的 distutils),以便他可以将“鲍勃包”上传到 Cheeseshop。由于 bob.py 已经指定了一个版本号,其用户可以通过编程方式访问,他希望即使他的用户现在从 Cheeseshop 获取它,相同的 API 也可以继续使用。

卡罗尔维护着几个命名空间包,每个包都是独立开发和发布的。为了让她的用户能够正确地指定对她的软件包的正确版本的依赖关系,她在命名空间包的 setup.py 文件中指定了版本号。由于卡罗尔希望每个软件包只更新一个版本号,因此她在她的模块中指定了版本号,并让 setup.py 在构建 sdist 存档时提取模块版本号。

大卫维护着标准库中的一个软件包,并且还为其他版本的 Python 生成了独立版本。标准库副本在模块中定义了版本号,独立分发版也使用相同的版本号。

理由

Python 模块(无论是在标准库中还是从第三方获取)长期以来都包含版本号。描述版本号已经存在着事实上的标准,多年来已经自然而然地发展出许多特设方法。通常,可以通过编程方式从模块中检索版本号,方法是导入模块并检查属性。经典的 Python distutils setup() 函数 [3] 描述了一个 version 参数,可以在其中指定版本的版本号。PEP 8 描述了使用名为 __version__ 的模块属性来记录使用关键字扩展的“Subversion、CVS 或 RCS”版本字符串。在 PEP 作者自己的电子邮件档案中,独立模块开发者最早使用 __version__ 模块属性的例子可以追溯到 1995 年。

版本信息的另一个例子是 sqlite3 [5] 模块,它有 sqlite_version_infoversionversion_info 属性。可能并不立即清楚哪个属性包含模块的版本号,哪个包含底层 SQLite3 库的版本号。

这份信息性 PEP 对既有的做法进行了规范,并推荐了描述模块版本号的标准方法,以及何时包含版本号以及何时不包含版本号的一些用例。模块作者完全自愿采用它;标准库中的打包工具将为这里定义的标准提供可选支持,而 Python 世界中的其他工具也可能遵守。

规范

  1. 一般来说,标准库中的模块不应该有版本号。它们隐式地包含了它们所包含的 Python 版本的版本号。
  2. 根据具体情况,标准库中以独立形式发布的模块也可能包含一个模块版本号,当包含在标准库中时,应该包含一个版本号,当单独打包时,也应该包含一个版本号。
  3. 当一个模块(或软件包)包含一个版本号时,该版本应该在 __version__ 属性中可用。
  4. 对于位于命名空间包内的模块,该模块应该包含 __version__ 属性。命名空间包本身不应该包含它自己的 __version__ 属性。
  5. __version__ 属性的值应该是一个字符串。
  6. 模块版本号应该符合 PEP 386 中指定的规范化版本格式。
  7. 模块版本号不应该包含版本控制系统提供的修订号,也不应该包含任何其他语义上不同的版本号(例如底层库的版本号)。
  8. 经典 distutils setup.py 文件中的 version 属性,或者 PEP 345 Version 元数据字段应该从 __version__ 字段中派生,反之亦然。

例子

从第三方包中检索版本号

>>> import bzrlib
>>> bzrlib.__version__
'2.3.0'

从作为独立模块发布的标准库包中检索版本号

>>> import email
>>> email.__version__
'5.1.0'

命名空间包的版本号

>>> import flufl.i18n
>>> import flufl.enum
>>> import flufl.lock

>>> print flufl.i18n.__version__
1.0.4
>>> print flufl.enum.__version__
3.1
>>> print flufl.lock.__version__
2.1

>>> import flufl
>>> flufl.__version__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__version__'
>>>

推导

模块版本号至少可以出现在两个地方,有时更多。例如,根据本 PEP 的规定,它们可以通过编程方式在模块的 __version__ 属性中获得。在经典的 distutils setup.py 文件中, setup() 函数接受一个 version 参数,而 distutils2 setup.cfg 文件有一个 version 键。版本号还必须进入 PEP 345 元数据,最好是在构建 sdist 存档时。模块作者希望只需指定一次版本号,所有其他使用方式都从该单个定义中派生出来,这非常理想。

这可以用许多方法完成,下面列出了其中一些方法。这些方法仅用于说明目的,并不打算成为最终的、完整的或包罗万象的。其他方法也是可能的,下面包含的一些方法可能存在局限性,无法在某些情况下使用。

假设埃莉将此属性添加到她的模块文件 elle.py

__version__ = '3.1.1'

经典 distutils

在经典的 distutils 中,将版本字符串添加到 setup.py 中的 setup() 函数中最简单的方法是像这样操作

from elle import __version__
setup(name='elle', version=__version__)

然而,根据 PEP 作者的经验,这在某些情况下可能会失败,例如当模块使用 2to3 程序进行自动 Python 3 转换时(因为 setup.pyelle 模块被转换之前由 Python 3 执行)。

在这种情况下,编写一小段代码从文件中解析 __version__ 而不是导入它并没有什么难度。无需提供太多细节,诸如 distutils2 之类的模块可能会提供一种从文件中解析版本字符串的方法。例如

from distutils2 import get_version
setup(name='elle', version=get_version('elle.py'))

Distutils2

由于 distutils2 风格的 setup.cfg 是声明性的,因此我们无法运行任何代码来提取 __version__ 属性,无论是通过导入还是通过解析。

与 distutils-sig [9] 协商后,提出了两个选择。两者都涉及将版本号包含在一个文件中,并在 setup.cfg 中声明该文件。当文件的全部内容包含版本号时,将使用 version-file

[metadata]
version-file: version.txt

当版本号包含在一个更大的文件中(例如 Python 代码)中,以至于必须解析该文件才能提取版本号时,将使用键 version-from-file

[metadata]
version-from-file: elle.py

将对冒号后面的文件名执行类似于上面描述的解析方法。执行此操作的确切方法将在相应的 distutils2 开发论坛中讨论。

另一种方法是仅在 setup.cfg 中定义版本号,并使用 pkgutil 模块 [8] 使其可以通过编程方式获得。例如,在 elle.py

from distutils2._backport import pkgutil
__version__ = pkgutil.get_distribution('elle').metadata['version']

PEP 376 元数据

PEP 376 定义了静态元数据的标准,但没有描述创建此元数据的过程。将派生的版本信息放置到 PEP 376 .dist-info 元数据中(在构建时而不是安装时)非常理想。这样,即使代码未安装,元数据也可用作自省。

参考


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

最后修改时间:2023-09-09 17:39:29 GMT