我正在走一个包含鸡蛋的目录,将这些鸡蛋添加到sys.path
。如果目录中有相同.egg的两个版本,我想只添加最新版本。
我有一个正则表达式r"^(?P<eggName>\w+)-(?P<eggVersion>[\d\.]+)-.+\.egg$
来从文件名中提取名称和版本。问题是比较版本号,这是一个像2.3.1
这样的字符串。
因为我正在比较字符串,所以2种类型超过10,但这对版本来说不正确。
>>> "2.3.1" > "10.1.1"
True
我可以做一些拆分,解析,转换为int等等,我最终会得到一个解决方法。但这是Python,not Java。有比较版本字符串的优雅方法吗?
答案 0 :(得分:273)
>>> from packaging import version
>>> version.parse("2.3.1") < version.parse("10.1.2")
True
>>> version.parse("1.3.a4") < version.parse("10.1.2")
True
>>> isinstance(version.parse("1.3.a4"), version.Version)
True
>>> isinstance(version.parse("1.3.xy123"), version.LegacyVersion)
True
>>> version.Version("1.3.xy123")
Traceback (most recent call last):
...
packaging.version.InvalidVersion: Invalid version: '1.3.xy123'
packaging.version.parse
是第三方实用程序,但由setuptools使用(因此您可能已安装它)并且符合当前PEP 440;如果版本符合要求,它将返回packaging.version.Version
,如果不符合,则返回packaging.version.LegacyVersion
。后者将始终在有效版本之前排序。
许多软件仍然使用的古代替代品是distutils.version
,内置但未记录且仅符合被取代的PEP 386;
>>> from distutils.version import LooseVersion, StrictVersion
>>> LooseVersion("2.3.1") < LooseVersion("10.1.2")
True
>>> StrictVersion("2.3.1") < StrictVersion("10.1.2")
True
>>> StrictVersion("1.3.a4")
Traceback (most recent call last):
...
ValueError: invalid version number '1.3.a4'
正如您所看到的那样,它将有效的PEP 440版本视为“不严格”,因此不符合现代Python关于有效版本的概念。
由于distutils.version
没有记录,here是相关的文档字符串。
答案 1 :(得分:83)
setuptools定义parse_version()
。这实现了PEP 0440 -- Version Identification,并且还能够解析不遵循PEP的版本。 easy_install
和pip
使用此函数来处理版本比较。来自docs:
解析PEP 440定义的项目版本字符串。返回的值将是表示版本的对象。可以将这些对象彼此进行比较并进行排序。排序算法如PEP 440所定义,另外任何不是有效PEP 440版本的版本将被认为小于任何有效的PEP 440版本,并且无效版本将使用原始算法继续排序。
在PEP 440出现之前,引用的“原始算法”在旧版本的文档中定义。
从语义上讲,格式是distutils'
StrictVersion
和LooseVersion
类之间的粗略交叉;如果你给它版本适用于StrictVersion
,那么他们将以相同的方式进行比较。否则,比较更像是LooseVersion
的“更聪明”形式。有可能创建会欺骗这个解析器的病态版本编码方案,但它们在实践中应该是非常罕见的。
documentation提供了一些示例:
如果你想确定你所选择的编号方案有效 你认为它的方式,你可以使用
pkg_resources.parse_version()
用于比较不同版本号的函数:>>> from pkg_resources import parse_version >>> parse_version('1.9.a.dev') == parse_version('1.9a0dev') True >>> parse_version('2.1-rc2') < parse_version('2.1') True >>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9') True
如果您没有使用setuptools,packaging项目会将此和其他与打包相关的功能拆分为一个单独的库。
from packaging import version
version.parse('1.0.3.dev')
from pkg_resources import parse_version
parse_version('1.0.3.dev')
答案 2 :(得分:48)
def versiontuple(v):
return tuple(map(int, (v.split("."))))
>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False
答案 3 :(得分:8)
将版本字符串转换为元组并从那里开始有什么问题?对我来说似乎很优雅
>>> (2,3,1) < (10,1,1)
True
>>> (2,3,1) < (10,1,1,1)
True
>>> (2,3,1,10) < (10,1,1,1)
True
>>> (10,3,1,10) < (10,1,1,1)
False
>>> (10,3,1,10) < (10,4,1,1)
True
@ kindall的解决方案是代码看起来有多好的一个简单例子。
答案 4 :(得分:7)
可以使用packaging个套件,您可以根据PEP-440以及旧版本来比较版本。
>>> from packaging.version import Version, LegacyVersion
>>> Version('1.1') < Version('1.2')
True
>>> Version('1.2.dev4+deadbeef') < Version('1.2')
True
>>> Version('1.2.8.5') <= Version('1.2')
False
>>> Version('1.2.8.5') <= Version('1.2.8.6')
True
传统版本支持:
>>> LegacyVersion('1.2.8.5-5-gdeadbeef')
<LegacyVersion('1.2.8.5-5-gdeadbeef')>
将旧版本与PEP-440版本进行比较。
>>> LegacyVersion('1.2.8.5-5-gdeadbeef') < Version('1.2.8.6')
True
答案 5 :(得分:3)
您可以使用semver包来确定某个版本是否满足semantic version要求。这与比较两个实际版本不同,但是是一种比较。
例如,版本3.6.0 + 1234应与3.6.0相同。
import semver
semver.match('3.6.0+1234', '==3.6.0')
# True
from packaging import version
version.parse('3.6.0+1234') == version.parse('3.6.0')
# False
from distutils.version import LooseVersion
LooseVersion('3.6.0+1234') == LooseVersion('3.6.0')
# False
答案 6 :(得分:2)
setuptools
的使用方式,它使用pkg_resources.parse_version
函数。它应该 PEP440 兼容。
示例:
#! /usr/bin/python
# -*- coding: utf-8 -*-
"""Example comparing two PEP440 formatted versions
"""
import pkg_resources
VERSION_A = pkg_resources.parse_version("1.0.1-beta.1")
VERSION_B = pkg_resources.parse_version("v2.67-rc")
VERSION_C = pkg_resources.parse_version("2.67rc")
VERSION_D = pkg_resources.parse_version("2.67rc1")
VERSION_E = pkg_resources.parse_version("1.0.0")
print(VERSION_A)
print(VERSION_B)
print(VERSION_C)
print(VERSION_D)
print(VERSION_A==VERSION_B) #FALSE
print(VERSION_B==VERSION_C) #TRUE
print(VERSION_C==VERSION_D) #FALSE
print(VERSION_A==VERSION_E) #FALSE
答案 7 :(得分:1)
根据Kindall的解决方案发布我的全部功能。我能够通过用前导零填充每个版本部分来支持与数字混合的任何字母数字字符。
虽然肯定不如他的单行功能那么漂亮,但它似乎与字母数字版本号很好地配合。 (如果您的版本控制系统中有长字符串,请务必正确设置zfill(#)
值。)
def versiontuple(v):
filled = []
for point in v.split("."):
filled.append(point.zfill(8))
return tuple(filled)
>>> versiontuple("10a.4.5.23-alpha") > versiontuple("2a.4.5.23-alpha")
True
>>> "10a.4.5.23-alpha" > "2a.4.5.23-alpha"
False
答案 8 :(得分:0)
我一直在寻找不会添加任何新依赖项的解决方案。查看以下(Python 3)解决方案:
class VersionManager:
@staticmethod
def compare_version_tuples(
major_a, minor_a, bugfix_a,
major_b, minor_b, bugfix_b,
):
"""
Compare two versions a and b, each consisting of 3 integers
(compare these as tuples)
version_a: major_a, minor_a, bugfix_a
version_b: major_b, minor_b, bugfix_b
:param major_a: first part of a
:param minor_a: second part of a
:param bugfix_a: third part of a
:param major_b: first part of b
:param minor_b: second part of b
:param bugfix_b: third part of b
:return: 1 if a > b
0 if a == b
-1 if a < b
"""
tuple_a = major_a, minor_a, bugfix_a
tuple_b = major_b, minor_b, bugfix_b
if tuple_a > tuple_b:
return 1
if tuple_b > tuple_a:
return -1
return 0
@staticmethod
def compare_version_integers(
major_a, minor_a, bugfix_a,
major_b, minor_b, bugfix_b,
):
"""
Compare two versions a and b, each consisting of 3 integers
(compare these as integers)
version_a: major_a, minor_a, bugfix_a
version_b: major_b, minor_b, bugfix_b
:param major_a: first part of a
:param minor_a: second part of a
:param bugfix_a: third part of a
:param major_b: first part of b
:param minor_b: second part of b
:param bugfix_b: third part of b
:return: 1 if a > b
0 if a == b
-1 if a < b
"""
# --
if major_a > major_b:
return 1
if major_b > major_a:
return -1
# --
if minor_a > minor_b:
return 1
if minor_b > minor_a:
return -1
# --
if bugfix_a > bugfix_b:
return 1
if bugfix_b > bugfix_a:
return -1
# --
return 0
@staticmethod
def test_compare_versions():
functions = [
(VersionManager.compare_version_tuples, "VersionManager.compare_version_tuples"),
(VersionManager.compare_version_integers, "VersionManager.compare_version_integers"),
]
data = [
# expected result, version a, version b
(1, 1, 0, 0, 0, 0, 1),
(1, 1, 5, 5, 0, 5, 5),
(1, 1, 0, 5, 0, 0, 5),
(1, 0, 2, 0, 0, 1, 1),
(1, 2, 0, 0, 1, 1, 0),
(0, 0, 0, 0, 0, 0, 0),
(0, -1, -1, -1, -1, -1, -1), # works even with negative version numbers :)
(0, 2, 2, 2, 2, 2, 2),
(-1, 5, 5, 0, 6, 5, 0),
(-1, 5, 5, 0, 5, 9, 0),
(-1, 5, 5, 5, 5, 5, 6),
(-1, 2, 5, 7, 2, 5, 8),
]
count = len(data)
index = 1
for expected_result, major_a, minor_a, bugfix_a, major_b, minor_b, bugfix_b in data:
for function_callback, function_name in functions:
actual_result = function_callback(
major_a=major_a, minor_a=minor_a, bugfix_a=bugfix_a,
major_b=major_b, minor_b=minor_b, bugfix_b=bugfix_b,
)
outcome = expected_result == actual_result
message = "{}/{}: {}: {}: a={}.{}.{} b={}.{}.{} expected={} actual={}".format(
index, count,
"ok" if outcome is True else "fail",
function_name,
major_a, minor_a, bugfix_a,
major_b, minor_b, bugfix_b,
expected_result, actual_result
)
print(message)
assert outcome is True
index += 1
# test passed!
if __name__ == '__main__':
VersionManager.test_compare_versions()
编辑:添加了具有元组比较的变量。当然,具有元组比较的变体更好,但是我一直在寻找具有整数比较的变体
答案 9 :(得分:0)
...然后回到简单... 对于简单的脚本,您可以使用:
import sys
needs = (3, 9) # or whatever
pvi = sys.version_info.major, sys.version_info.minor
稍后在您的代码中
try:
assert pvi >= needs
except:
print("will fail!")
# etc.
答案 10 :(得分:-3)
我会更多地选择touple选项,进行测试,使用LooseVersion,我确实在我的测试中获得第二大选项,(可能是因为我第一次使用该库而做了一些事情)
import itertools
from distutils.version import LooseVersion, StrictVersion
lista_de_frameworks = ["1.1.1", "1.2.5", "10.5.2", "3.4.5"]
for a, b in itertools.combinations(lista_de_frameworks, 2):
if LooseVersion(a) < LooseVersion(b):
big = b
print big
list_test = []
for a in lista_de_frameworks:
list_test.append( tuple(map(int, (a.split(".")))))
print max(list_test)
这就是我得到的:
3.4.5 with Loose
(10,5,2)和操纵