Answers:
要在运行时从程序包内部检索版本(您的问题实际上是在问什么),可以使用:
import pkg_resources # part of setuptools
version = pkg_resources.require("MyProject")[0].version
如果要沿另一方向走动(这似乎是其他答案作者似乎认为您正在询问的问题),请将版本字符串放在单独的文件中,然后在中读取该文件的内容setup.py
。
您可以在包中用__version__
一行创建一个version.py ,然后使用来从setup.py读取它execfile('mypackage/version.py')
,以便__version__
在setup.py命名空间中进行设置。
如果您想要一种更简单的方法来适用于所有Python版本,甚至可能需要访问版本字符串的非Python语言,请执行以下操作:
将版本字符串存储为纯文本文件(例如)的唯一内容VERSION
,并在期间读取该文件setup.py
。
version_file = open(os.path.join(mypackage_root_dir, 'VERSION'))
version = version_file.read().strip()
这样,同一个VERSION
文件在任何其他程序中都可以很好地工作,即使在非Python程序中也是如此,并且您只需要在一个位置更改所有程序的版本字符串即可。
顺便说一句,请不要按照此处另一个答案的建议从setup.py导入软件包:它似乎对您有用(因为您已经安装了软件包的依赖项),但是会对软件包的新用户造成严重破坏,因为如果不先手动安装依赖项,他们将无法安装您的软件包。
execfile
作品真的很好......但(可惜)不使用Python 3下工作
with open('mypackage/version.py') as f: exec(f.read())
代替execfile('mypackage/version.py')
。(来自stackoverflow.com/a/437857/647002)
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
mymodule
在安装过程中导入如果您是setup.py
进口公司,mymodule
则在设置过程中很可能会收到ImportError
。当您的软件包具有依赖项时,这是一个非常常见的错误。如果您的程序包除内置程序外没有其他依赖项,则可能是安全的。但是,这不是一个好习惯。这样做的原因是它不是面向未来的。说明天您的代码需要使用其他依赖项。
__version__
?如果您进行硬编码__version__
,setup.py
则它可能与您模块中附带的版本不匹配。为了保持一致,您可以将其放在一个地方,并在需要时从同一地方阅读。使用import
您可能会遇到问题1。
setuptools
您可以结合使用open
,exec
并提供dict 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'
这样,该版本将随模块一起提供,并且在安装期间尝试导入缺少依赖项(尚未安装)的模块时不会出现问题。
最好的技术是__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是安装它们的原因。
__version__
某种方式损坏,则导入也将损坏。Python不知道如何仅解释所需的语句。@pjeby是正确的:如果您的模块需要导入其他模块,则可能尚未安装它们,这将会很混乱。如果您谨慎地确保导入不会引起其他导入的长链影响,则可以使用此技术。
pip install <packagename>
出现以下错误:ImportError: No module named <packagename>
。请警告您的读者,您不能在尚未安装该软件包的环境中运行此类的setup.py文件!
我对这些答案不满意...不想使用setuptools,也不想为单个变量制作整个单独的模块,所以我想到了这些。
当您确定主模块为pep8样式并将保持这种状态时:
version = '0.30.unknown'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
_, _, version = line.replace("'", '').split()
break
如果您要格外小心并使用真实的解析器:
import ast
version = '0.30.unknown2'
with file('mypkg/mymod.py') as f:
for line in f:
if line.startswith('__version__'):
version = ast.parse(line).body[0].value.s
break
setup.py在某种程度上是一个一次性模块,因此如果它有点难看,则不是问题。
更新:有趣的是,近年来,我已经摆脱了这一点,开始使用名为的软件包中的单独文件meta.py
。我在其中放置了很多可能需要经常更改的元数据。因此,不仅仅是为了一个价值。
ast.get_docstring()
与其他内容.split('\n')[0].strip()
一起使用,以自动description
从源代码中进行填写。要保持同步的另一件事
在源代码树中创建文件,例如在yourbasedir / yourpackage / _version.py中。让该文件仅包含一行代码,如下所示:
__version__ = "1.1.0-r4704"
然后在setup.py中,打开该文件并解析出如下版本号:
verstr =“未知” 尝试: verstrline = open('yourpackage / _version.py',“ rt”)。read() 除了EnvironmentError: 通过#好的,没有版本文件。 其他: VSRE = r“ ^ __ version__ = ['\”]([^ \\]] *)['\“]” mo = re.search(VSRE,verstrline,re.M) 如果莫: verstr = mo.group(1) 其他: 引发RuntimeError(“无法在您的package / _version.py中找到版本”)
最后,在yourbasedir/yourpackage/__init__.py
import _version中,如下所示:
__version__ =“未知” 尝试: 从_version导入__version__ 除了ImportError: #我们正在没有_version.py的树中运行,因此我们不知道我们的版本是什么。 通过
我维护的“ pyutil”包就是一个执行此操作的代码示例。(请参阅PyPI或Google搜索-stackoverflow不允许我在此答案中包含指向它的超链接。)
@pjeby是正确的,您不应从其自己的setup.py导入软件包。当您通过创建一个新的Python解释器并在其中首先执行setup.py对其python setup.py
进行测试时,它将起作用,但是在某些情况下它将无法工作。那是因为import youpackage
这并不意味着要读取名为“ yourpackage”的目录的当前工作目录,而是要在当前目录中查找sys.modules
键“ yourpackage”,然后在不存在的情况下执行各种操作。所以它总是在工作时起作用,python setup.py
因为您有一个空的sys.modules
,但这通常并不起作用。
例如,如果py2exe在打包应用程序的过程中正在执行setup.py怎么办?我见过这样的情况,其中py2exe会在软件包上放置错误的版本号,因为该软件包正在从中获取其版本号import myownthing
在其setup.py中,但是以前在py2exe运行期间导入了该软件包的其他版本。同样,如果setuptools,easy_install,distribution或distutils2尝试在安装依赖于您的软件包的其他软件包的过程中尝试构建软件包,该怎么办?然后,在评估setup.py时是否可以导入您的软件包,或者在此Python解释器的生命周期中是否已经导入了您的软件包的版本,或者导入您的软件包是否需要先安装其他软件包? ,或有副作用,可以改变结果。我在尝试重新使用Python软件包时遇到了很多困难,这导致py2exe和setuptools之类的工具出现问题,因为它们的setup.py会导入软件包本身以查找其版本号。
顺便说一句,这种技术可以很好地与自动yourpackage/_version.py
为您创建文件的工具配合使用,例如,通过读取修订控制历史记录并根据修订控制历史记录中的最新标记写出版本号来实现。这是一个针对darcs的工具:http : //tahoe-lafs.org/trac/darcsver/browser/trunk/README.rst,这是一个对git进行相同操作的代码片段:http:// github .com / warner / python-ecdsa / blob / 0ed702a9d4057ecf33eea969b8cf280eaccd89a1 / setup.py#L34
使用正则表达式并根据元数据字段具有如下格式,这也应该起作用:
__fieldname__ = 'value'
在setup.py的开头使用以下命令:
import re
main_py = open('yourmodule.py').read()
metadata = dict(re.findall("__([a-z]+)__ = '([^']+)'", main_py))
之后,您可以像下面这样在脚本中使用元数据:
print 'Author is:', metadata['author']
print 'Version is:', metadata['version']
为了避免导入文件(从而执行其代码),可以解析文件并version
从语法树中恢复属性:
# assuming 'path' holds the path to the file
import ast
with open(path, 'rU') as file:
t = compile(file.read(), path, 'exec', ast.PyCF_ONLY_AST)
for node in (n for n in t.body if isinstance(n, ast.Assign)):
if len(node.targets) == 1:
name = node.targets[0]
if isinstance(name, ast.Name) and \
name.id in ('__version__', '__version_info__', 'VERSION'):
v = node.value
if isinstance(v, ast.Str):
version = v.s
break
if isinstance(v, ast.Tuple):
r = []
for e in v.elts:
if isinstance(e, ast.Str):
r.append(e.s)
elif isinstance(e, ast.Num):
r.append(str(e.n))
version = '.'.join(r)
break
这段代码试图在模块的顶层查找__version__
或VERSION
返回字符串值。右侧可以是字符串或元组。
有上千种为猫皮的方法-这是我的:
# Copied from (and hacked):
# https://github.com/pypa/virtualenv/blob/develop/setup.py#L42
def get_version(filename):
import os
import re
here = os.path.dirname(os.path.abspath(__file__))
f = open(os.path.join(here, filename))
version_file = f.read()
f.close()
version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]",
version_file, re.M)
if version_match:
return version_match.group(1)
raise RuntimeError("Unable to find version string.")
从@ gringo-suave 清理https://stackoverflow.com/a/12413800:
from itertools import ifilter
from os import path
from ast import parse
with open(path.join('package_name', '__init__.py')) as f:
__version__ = parse(next(ifilter(lambda line: line.startswith('__version__'),
f))).body[0].value.s
现在这很麻烦,需要进行一些改进(我可能错过了pkg_resources中甚至可能有一个未发现的成员调用),但是我根本看不出为什么这行不通,也为什么至今没有人建议这样做(在Google周围搜索没有出现)...请注意,这是Python 2.x,需要pkg_resources(叹气):
import pkg_resources
version_string = None
try:
if pkg_resources.working_set is not None:
disto_obj = pkg_resources.working_set.by_key.get('<my pkg name>', None)
# (I like adding ", None" to gets)
if disto_obj is not None:
version_string = disto_obj.version
except Exception:
# Do something
pass
我们希望将有关我们程序包的元信息放入pypackagery
中__init__.py
,但由于PJ Eby已经指出(请参阅他的回答和有关比赛条件的警告),它具有第三方依赖性,因此无法这样做。
我们通过创建一个仅pypackagery_meta.py
包含元信息的单独模块来解决该问题:
"""Define meta information about pypackagery package."""
__title__ = 'pypackagery'
__description__ = ('Package a subset of a monorepo and '
'determine the dependent packages.')
__url__ = 'https://github.com/Parquery/pypackagery'
__version__ = '1.0.0'
__author__ = 'Marko Ristin'
__author_email__ = 'marko.ristin@gmail.com'
__license__ = 'MIT'
__copyright__ = 'Copyright 2018 Parquery AG'
然后将元信息导入packagery/__init__.py
:
# ...
from pypackagery_meta import __title__, __description__, __url__, \
__version__, __author__, __author_email__, \
__license__, __copyright__
# ...
并最终用于setup.py
:
import pypackagery_meta
setup(
name=pypackagery_meta.__title__,
version=pypackagery_meta.__version__,
description=pypackagery_meta.__description__,
long_description=long_description,
url=pypackagery_meta.__url__,
author=pypackagery_meta.__author__,
author_email=pypackagery_meta.__author_email__,
# ...
py_modules=['packagery', 'pypackagery_meta'],
)
您必须pypackagery_meta
使用py_modules
setup参数将其包含在包中。否则,您将无法在安装时导入它,因为打包的发行版将缺少它。
简单明了,创建一个source/package_name/version.py
包含以下内容的文件:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
__version__ = "2.6.9"
然后,在文件上source/package_name/__init__.py
,导入供其他人使用的版本:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from .version import __version__
现在,您可以将其放在 setup.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
version_file = open( filepath )
__version__ ,= re.findall( '__version__ = "(.*)"', version_file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
finally:
version_file.close()
与Python测试了这个2.7
,3.3
,3.4
,3.5
,3.6
和3.7
在Linux,Windows和Mac OS。我在包装上使用了针对所有这些平台的集成和单元测试。您可以从.travis.yml
和appveyor.yml
在此处查看结果:
备用版本使用上下文管理器:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
try:
filepath = 'source/package_name/version.py'
with open( filepath ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
您还可以使用该codecs
模块在Python 2.7
和3.6
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
try:
filepath = 'source/package_name/version.py'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
如果要使用Python C扩展在C / C ++中100%编写Python模块,则可以执行相同的操作,但要使用C / C ++而不是Python。
在这种情况下,创建以下内容setup.py
:
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import re
import sys
import codecs
from setuptools import setup, Extension
try:
filepath = 'source/version.h'
with codecs.open( filepath, 'r', errors='ignore' ) as file:
__version__ ,= re.findall( '__version__ = "(.*)"', file.read() )
except Exception as error:
__version__ = "0.0.1"
sys.stderr.write( "Warning: Could not open '%s' due %s\n" % ( filepath, error ) )
setup(
name = 'package_name',
version = __version__,
package_data = {
'': [ '**.txt', '**.md', '**.py', '**.h', '**.hpp', '**.c', '**.cpp' ],
},
ext_modules = [
Extension(
name = 'package_name',
sources = [
'source/file.cpp',
],
include_dirs = ['source'],
)
],
)
从文件中读取版本version.h
:
const char* __version__ = "1.0.12";
但是,不要忘记创建MANIFEST.in
包含version.h
文件:
include README.md
include LICENSE.txt
recursive-include source *.h
并通过以下方式集成到主要应用程序中:
#include <Python.h>
#include "version.h"
// create the module
PyMODINIT_FUNC PyInit_package_name(void)
{
PyObject* thismodule;
...
// https://docs.python.org/3/c-api/arg.html#c.Py_BuildValue
PyObject_SetAttrString( thismodule, "__version__", Py_BuildValue( "s", __version__ ) );
...
}
参考文献:
将包部署到服务器和索引包的文件命名约定:
pip动态版本转换的示例:
赢得:
苹果电脑:
from setuptools_scm import get_version
def _get_version():
dev_version = str(".".join(map(str, str(get_version()).split("+")[0]\
.split('.')[:-1])))
return dev_version
找到示例setup.py从git commit调用匹配的动态pip版本
setup(
version=_get_version(),
name=NAME,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
classifiers=CLASSIFIERS,
# add few more for wheel wheel package ...conversion
)
我正在使用以下环境变量
VERSION = 0.0.0 python setup.py sdist bdist_wheel
在setup.py中
import os
setup(
version=os.environ['VERSION'],
...
)
为了与打包程序版本进行一致性检查,我使用以下脚本。
PKG_VERSION=`python -c "import pkg; print(pkg.__version__)"`
if [ $PKG_VERSION == $VERSION ]; then
python setup.py sdist bdist_wheel
else
echo "Package version differs from set env variable"
fi