如何在我的包中获取setup.py中定义的版本(setuptools)?

208 投票
21 回答
148570 浏览
提问于 2025-04-15 17:59

我该如何从我的包中获取在 setup.py 文件里定义的版本号(比如用于 --version 或其他用途)呢?

21 个回答

25

最好的方法是在你的产品代码里定义一个叫 __version__ 的变量,然后在 setup.py 文件中引入这个变量。这样,你就可以在运行的模块中读取这个值,而且只需要在一个地方定义它。

setup.py 中的值不会被安装,而且在安装完成后,setup.py 文件也不会再存在。

我在 coverage.py 中做的一个例子是:

# coverage/__init__.py
__version__ = "3.2"


# setup.py
from coverage import __version__

setup(
    name = 'coverage',
    version = __version__,
    ...
    )

更新(2017年):coverage.py 现在不再通过导入自己来获取版本号了。导入自己的代码可能会导致无法安装,因为你的产品代码会尝试导入一些依赖项,而这些依赖项还没有被安装,因为是 setup.py 来负责安装它们的。

47

示例研究:mymodule

想象一下这样的配置:

setup.py
mymodule/
        / __init__.py
        / version.py
        / myclasses.py

然后想象一个常见的场景,你有一些依赖项,setup.py 看起来像这样:

setup(...
    install_requires=['dep1','dep2', ...]
    ...)

还有一个示例的 __init__.py

from mymodule.myclasses import *
from mymodule.version import __version__

再比如 myclasses.py

# these are not installed on your system.
# importing mymodule.myclasses would give ImportError
import dep1
import dep2

问题 #1:在设置时导入 mymodule

如果你的 setup.py 导入了 mymodule,那么在设置过程中你很可能会遇到 ImportError 错误。这是一个很常见的错误,尤其是当你的包有依赖项时。如果你的包除了内置库之外没有其他依赖项,可能会安全一些;但这并不是一个好习惯。原因在于,这样做不够稳妥;比如说,明天你的代码需要使用其他依赖项。

问题 #2:我的 __version__ 在哪里?

如果你在 setup.py 中硬编码了 __version__,那么它可能和你在模块中发布的版本不一致。为了保持一致性,你应该把它放在一个地方,并在需要的时候从同一个地方读取。使用 import 的话,你可能会遇到问题 #1。

解决方案:使用 setuptools

你可以结合使用 openexec,并为 exec 提供一个字典来添加变量:

# setup.py
from setuptools import setup, find_packages
from distutils.util import convert_path

main_ns = {}
ver_path = convert_path('mymodule/version.py')
with open(ver_path) as ver_file:
    exec(ver_file.read(), main_ns)

setup(...,
    version=main_ns['__version__'],
    ...)

然后在 mymodule/version.py 中暴露版本信息:

__version__ = 'some.semantic.version'

这样,版本信息就和模块一起发布了,你在设置时就不会因为导入缺少依赖的模块而遇到问题(这些依赖还没有安装)。

314

查询已安装分发版的版本信息

如果你想在运行时从你的包中获取版本信息(这似乎是你问题的核心),你可以使用:

import pkg_resources  # part of setuptools
version = pkg_resources.require("MyProject")[0].version

在安装时存储版本信息

如果你想反过来做(这似乎是其他回答者认为你在问的),可以把版本信息放在一个单独的文件里,然后在 setup.py 中读取这个文件的内容。

你可以在你的包里创建一个 version.py 文件,里面写上 __version__ 这一行,然后在 setup.py 中用 execfile('mypackage/version.py') 来读取它,这样就能在 setup.py 的命名空间中设置 __version__

关于安装时竞争条件的警告

顺便提一下,不要像这里其他回答中建议的那样从 setup.py 中导入你的包:这看起来对你是有效的(因为你已经安装了包的依赖),但对新用户来说会造成很大麻烦,因为他们在安装你的包时必须先手动安装依赖。

撰写回答