是否有一种标准方法将版本字符串与Python包相关联,以便我可以执行以下操作?
import foo
print(foo.version)
我想必须有某种方式可以检索到那些数据,而不需要任何额外的硬编码,因为次要/主要字符串已经在setup.py
中指定了。我发现的另一种替代方案是,在我的foo/__init__.py
中使用import __version__
,然后由setup.py
生成__version__.py
。
是否有一种标准方法将版本字符串与Python包相关联,以便我可以执行以下操作?
import foo
print(foo.version)
我想必须有某种方式可以检索到那些数据,而不需要任何额外的硬编码,因为次要/主要字符串已经在setup.py
中指定了。我发现的另一种替代方案是,在我的foo/__init__.py
中使用import __version__
,然后由setup.py
生成__version__.py
。
__version_info__
?(这是你自己“发明”的双下划线词汇。)[当詹姆斯发表评论时,下划线在注释中没有任何作用,现在它们表示强调,所以詹姆斯确实也写了 __version_info__
。---编者注。] - Roger Pate__version__
的内容遵循 PEP 386 中的规则非常理想,这样可以可靠地比较版本号。 - Daira Hopwood我使用单个_version.py
文件作为存储版本信息的“唯一权威地点”:
它提供了一个__version__
属性。
它提供了标准的元数据版本。因此,pkg_resources
或其他解析包元数据(EGG-INFO和/或PKG-INFO,PEP 0345)的工具将检测到它。
在构建软件包时,它不会导入您的软件包(或任何其他内容),这可能会在某些情况下引起问题(请参见下面关于这可能引起什么问题的评论)。
只有一个位置记录版本号,因此,在版本号更改时只有一个位置需要更改,减少了不一致版本的机会。
它的工作方式如下:存储版本号的“唯一权威地点”是一个名为_version.py
的.py文件,位于您的Python包中,例如在myniftyapp/_version.py
中。该文件是一个Python模块,但是您的setup.py不会导入它!(这将破坏功能3。)相反,您的setup.py知道这个文件的内容非常简单,类似于:
__version__ = "3.6.5"
因此,您的setup.py会打开文件并解析它,代码类似于:
import re
VERSIONFILE="myniftyapp/_version.py"
verstrline = open(VERSIONFILE, "rt").read()
VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]"
mo = re.search(VSRE, verstrline, re.M)
if mo:
verstr = mo.group(1)
else:
raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,))
然后,你的setup.py将该字符串作为"version"参数的值传递给setup()
,从而满足特性2。
为了满足特性1,你可以在运行时(而不是设置时间),像这样从myniftyapp/__init__.py
导入_version文件来引用你的包:
from _version import __version__
这里是我多年来一直在使用的技巧的示例。
那个示例中的代码有点复杂,但我在这条评论中写入的简化示例应该是一个完整的实现。
这里是导入版本的示例代码。
如果您对这种方法有任何疑问,请告诉我。
setup.py
。此外,如果它尝试在执行所有依赖项之前进行完整的深度优先搜索,那么如果存在循环依赖,则会被卡住。但是,如果它在安装依赖项之前尝试构建此软件包,那么如果您从 setup.py
导入软件包,则不一定能够导入其依赖项或其依赖项的正确版本。 - Zookoexecfile("myniftyapp/_version.py")
。建议在https://dev59.com/V3I95IYBdhLWcg3w_zWs#2073599中提到,那里的讨论也可能会有所帮助。 - medmunds经过13年的Python编程和管理各种包,我得出结论,自己动手可能不是最好的方法。
我开始使用pbr
包来处理我的包中的版本控制。如果您使用git作为您的源代码管理工具,这将像魔法一样适合您的工作流程,可以节省您数周的工作时间(您会惊讶于这个问题可能有多么复杂)。
截至今日,pbr每月下载量达到了1200万次,并且达到这个水平并没有采用任何肮脏的技巧。只是一个简单的解决常见的打包问题的方法。
pbr
可以承担更多的软件包维护负担,并且不仅限于版本控制,但它不会强制您采用所有的优点。
因此,为了让您对在一个提交中采用pbr的情况有一个想法,请看切换打包到pbr
您可能会注意到版本根本没有存储在仓库中。PBR会从Git分支和标签中检测它。
当您打包或安装应用程序时,不必担心没有git存储库时会发生什么,因为pbr会“编译”并缓存版本,因此在运行时没有对git的依赖性。
这是我迄今为止看过的最好的解决方案,它也解释了原因:
在yourpackage/version.py
内部:
# Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
__version__ = '0.12'
__init__.py
文件中:from .version import __version__
setup.py
内容如下:
exec(open('yourpackage/version.py').read())
setup(
...
version=__version__,
...
如果您知道其他更好的方法,请告诉我。
from .version import __version__
呢? - Aprillionsetup.py
时包没有被加载 - 你可以试试,会得到一个错误(至少我是这样的 :-)) - darthbith根据被推迟的 [紧急新闻:被拒绝了] PEP 396 (模块版本号),提出了一种实现此目的的建议方式。它描述了一个(可选的)标准供模块遵循,同时附带了理由。这是一个片段:
- 当一个模块(或包)包含一个版本号时,该版本号应该在
__version__
属性中提供。
- 对于位于命名空间包内的模块,应包含
__version__
属性。命名空间包本身不应包含自己的__version__
属性。
__version__
属性的值应该是一个字符串。
还有一种比其他答案稍微简单的替代方法:
__version_info__ = ('1', '2', '3')
__version__ = '.'.join(__version_info__)
(而且,使用 str()
将自动增量部分转换为字符串将是相当简单的。)__version_info__
时倾向于使用前面提到的版本,并将其存储为一组整数;然而,我不太明白这样做的意义所在,因为我怀疑除了好奇心或自动增量之外,你不会在任何目的上对版本号的某些部分执行数学运算(就算这种情况下,int()
和 str()
也可以非常容易地使用)。 (另一方面,有可能其他人的代码期望一个数字元组而不是一个字符串元组,从而失败。)__version_info__
(或它被称为什么)存储为整数值的元组将允许更高效的版本比较。__version_info__ = (1,2,3)
。 - orip__version__ = '.'.join(__version_info__)
的问题在于 __version_info__ = ('1', '2', 'beta')
会变成 1.2.beta
,而不是 1.2beta
或者 1.2 beta
。 - nagisa这里的许多解决方案都忽略了git
版本标签,这意味着您仍然必须在多个地方跟踪版本(不好)。我采用以下目标来解决这个问题:
git
存储库中的标签中派生所有Python版本引用git tag
/push
和setup.py upload
步骤,而不需要输入任何内容。通过“make release”命令,找到Git存储库中最后一个标记的版本并递增。该标记被推回给“origin”。
Makefile
将版本存储在src/_version.py
中,setup.py
会读取它,并包含在发布中。请勿将_version.py
检入源代码控制!
setup.py
命令从package.__version__
读取新的版本字符串。
# remove optional 'v' and trailing hash "v1.0-N-HASH" -> "v1.0-N"
git_describe_ver = $(shell git describe --tags | sed -E -e 's/^v//' -e 's/(.*)-.*/\1/')
git_tag_ver = $(shell git describe --abbrev=0)
next_patch_ver = $(shell python versionbump.py --patch $(call git_tag_ver))
next_minor_ver = $(shell python versionbump.py --minor $(call git_tag_ver))
next_major_ver = $(shell python versionbump.py --major $(call git_tag_ver))
.PHONY: ${MODULE}/_version.py
${MODULE}/_version.py:
echo '__version__ = "$(call git_describe_ver)"' > $@
.PHONY: release
release: test lint mypy
git tag -a $(call next_patch_ver)
$(MAKE) ${MODULE}/_version.py
python setup.py check sdist upload # (legacy "upload" method)
# twine upload dist/* (preferred method)
git push origin master --tags
release
目标总是增加第三个版本数字,但您可以使用 next_minor_ver
或 next_major_ver
来增加其他数字。这些命令依赖于位于 repo 根目录中的 versionbump.py
脚本。
"""An auto-increment tool for version strings."""
import sys
import unittest
import click
from click.testing import CliRunner # type: ignore
__version__ = '0.1'
MIN_DIGITS = 2
MAX_DIGITS = 3
@click.command()
@click.argument('version')
@click.option('--major', 'bump_idx', flag_value=0, help='Increment major number.')
@click.option('--minor', 'bump_idx', flag_value=1, help='Increment minor number.')
@click.option('--patch', 'bump_idx', flag_value=2, default=True, help='Increment patch number.')
def cli(version: str, bump_idx: int) -> None:
"""Bumps a MAJOR.MINOR.PATCH version string at the specified index location or 'patch' digit. An
optional 'v' prefix is allowed and will be included in the output if found."""
prefix = version[0] if version[0].isalpha() else ''
digits = version.lower().lstrip('v').split('.')
if len(digits) > MAX_DIGITS:
click.secho('ERROR: Too many digits', fg='red', err=True)
sys.exit(1)
digits = (digits + ['0'] * MAX_DIGITS)[:MAX_DIGITS] # Extend total digits to max.
digits[bump_idx] = str(int(digits[bump_idx]) + 1) # Increment the desired digit.
# Zero rightmost digits after bump position.
for i in range(bump_idx + 1, MAX_DIGITS):
digits[i] = '0'
digits = digits[:max(MIN_DIGITS, bump_idx + 1)] # Trim rightmost digits.
click.echo(prefix + '.'.join(digits), nl=False)
if __name__ == '__main__':
cli() # pylint: disable=no-value-for-parameter
这个功能会从git
中处理和递增版本号重活。
my_module/_version.py
文件被导入到 my_module/__init__.py
中。在这里放置任何你想要与你的模块一起分发的静态安装配置。
from ._version import __version__
__author__ = ''
__email__ = ''
my_module
模块中读取版本信息。from setuptools import setup, find_packages
pkg_vars = {}
with open("{MODULE}/_version.py") as fp:
exec(fp.read(), pkg_vars)
setup(
version=pkg_vars['__version__'],
...
...
)
当然,为了让所有这些工作正常运行,您必须在您的存储库中至少有一个版本标签。
git tag -a v0.0.1
_version.py
文件? - Stevoisiak我在包目录中使用了一个JSON文件,这符合Zooko的要求。
在pkg_dir/pkg_info.json
文件内:
{"version": "0.1.0"}
在 setup.py
文件中:
from distutils.core import setup
import json
with open('pkg_dir/pkg_info.json') as fp:
_info = json.load(fp)
setup(
version=_info['version'],
...
)
在pkg_dir/__init__.py
文件中:
import json
from os.path import dirname
with open(dirname(__file__) + '/pkg_info.json') as fp:
_info = json.load(fp)
__version__ = _info['version']
我还在pkg_info.json
中放置了其他信息,例如作者。我喜欢使用JSON,因为我可以自动化管理元数据。
setup.py
(setuptools)文件并获取版本。__init__.py
和源代码控制),例如bump2version,changes或zest.releaser。__version__
。setup.py
发布设置该值,并使用importlib.metadata在运行时进行拾取。(警告,有3.8之前和3.8之后的版本。)sample/__init__.py
中的__version__
,并在setup.py
中导入样本。python3 setup.py --version
将直接报告版本号。__version__
在Python中是半标准之外,__version_info__
也是一个元组。在简单情况下,您可以执行以下操作:__version__ = '1.2.3'
__version_info__ = tuple([ int(num) for num in __version__.split('.')])
你可以从文件或其他地方获取__version__
字符串。
__version_info__
使用起源的任何参考资料/链接吗? - Craig McQueen__version_info__ = (1, 2, 3)
和 __version__ = '.'.join(map(str, __version_info__))
)。 - Eric O. Lebigot__version__ = '.'.join(str(i) for i in __version_info__)
- 稍微长一些但更符合Python风格。 - ArtOfWarfarei
没有意义,version_num
有点长且模糊)。我甚至认为Python中存在map()
函数是应该在这里使用的强烈提示,因为我们需要做的就是map()
的典型用例(与现有函数一起使用)-我没有看到其他合理的用途。 - Eric O. LebigotArrow 以一种有趣的方式处理它。
现在(自从2e5031b版本起)
在 arrow/__init__.py
文件中:
__version__ = 'x.y.z'
setup.py
文件中:from arrow import __version__
setup(
name='arrow',
version=__version__,
# [...]
)
Before
In arrow/__init__.py
:
__version__ = 'x.y.z'
VERSION = __version__
setup.py
文件中:def grep(attrname):
pattern = r"{0}\W*=\W*'([^']+)'".format(attrname)
strval, = re.findall(pattern, file_text)
return strval
file_text = read(fpath('arrow/__init__.py'))
setup(
name='arrow',
version=grep('__version__'),
# [...]
)
file_text
是什么? - elypip install -e .
时。setup.py 绝对不应依赖于导入正在安装过程中的软件包以确定部署参数。哎呀。 - ely
setup.py
中放置版本即可。请参见此问题。 - saajsetup.py
有一个版本(它必须有),为什么package_name.__version__
不能正常工作并显示该版本呢? - eric