我试图得到它以便1.7.0在1.7.0.rc0之后,但在1.8.0之前,因为它应该排序版本。我认为LooseVersion的重点在于它正确处理了这种事情的排序和比较。
>>> from distutils.version import LooseVersion
>>> versions = ["1.7.0", "1.7.0.rc0", "1.8.0"]
>>> lv = [LooseVersion(v) for v in versions]
>>> sorted(lv, reverse=True)
[LooseVersion ('1.8.0'), LooseVersion ('1.7.0.rc0'), LooseVersion ('1.7.0')]
答案 0 :(得分:9)
>>> from distutils.version import LooseVersion
>>> versions = ["1.7.0", "1.7.0rc0", "1.11.0"]
>>> sorted(versions, key=LooseVersion)
['1.7.0', '1.7.0rc0', '1.11.0']
来自文档
无政府主义者和软件现实主义者的版本编号。实现 如上所述的版本号类的标准接口。一个 版本号由一系列数字组成,由两者分隔 句号或字母串。比较版本号时, 数字组件将在数字上进行比较,并按字母顺序进行比较 词汇组成词汇 ...
事实上,没有无效的版本号 这个计划;比较规则简单且可预测,但是 可能并不总是给出你想要的结果(对于某些定义 “想”)。
所以你看到没有专门处理“rc”的聪明才智
您可以看到版本号如何按此分解
>>> LooseVersion('1.7.0rc0').version
[1, 7, 0, 'rc', 0]
答案 1 :(得分:7)
MAJOR EDIT:旧答案太不熟悉了。这是两个更漂亮的解决方案。
所以,我目前看到了实现预期订购的三种方式,在实际发布之前发布候选“rc”。
StrictVersion
Version
类以添加对任意标记和标记排序的支持from distutils.version import LooseVersion
versions = ["1.7.0", "1.7.0.rc0", "1.8.0"]
lv = [LooseVersion(v) for v in versions]
lv.sort()
sorted_rc = [v.vstring for v in lv]
import re
p = re.compile('rc\\d+$')
i = 0
# skip the first RCs
while i + 1 < len(sorted_rc):
m = p.search(sorted_rc[i])
if m:
i += 1
else:
break
while i + 1 < len(sorted_rc):
tmp = sorted_rc[i]
m = p.search(sorted_rc[i+1])
if m and sorted_rc[i+1].startswith(tmp):
sorted_rc[i] = sorted_rc[i+1]
sorted_rc[i+1] = tmp
i += 1
我得到了:
['1.7.0rc0', '1.7.0', '1.11.0']
如果distutils.version
被允许写为StrictVersion
或1.7.0.rc0
,则1.7.0a0
包还会有另一个类1.7.0b0
来完成这项工作alpha或beta版本。
那是:
from distutils.version import StrictVersion
versions = ["1.7.0", "1.7.0b0", "1.11.0"]
sorted(versions, key=StrictVersion)
这给出了:
['1.7.0b0', '1.7.0', '1.11.0']
可以使用re模块完成从一种形式到另一种形式的翻译。
先前解决方案的明显问题是StrictVersion
缺乏灵活性。将version_re
类属性更改为使用rc
而不是a
或b
,即使它接受1.7.1rc0
,仍会将其打印为1.7.1r0
(从python 2.7.3开始。
我们可以通过实现自己的自定义版本类来实现它。这可以这样做,通过一些单元测试来确保至少在某些情况下的正确性:
#!/usr/bin/python
# file: version2.py
from distutils import version
import re
import functools
@functools.total_ordering
class NumberedVersion(version.Version):
"""
A more flexible implementation of distutils.version.StrictVersion
This implementation allows to specify:
- an arbitrary number of version numbers:
not only '1.2.3' , but also '1.2.3.4.5'
- the separator between version numbers:
'1-2-3' is allowed when '-' is specified as separator
- an arbitrary ordering of pre-release tags:
1.1alpha3 < 1.1beta2 < 1.1rc1 < 1.1
when ["alpha", "beta", "rc"] is specified as pre-release tag list
"""
def __init__(self, vstring=None, sep='.', prerel_tags=('a', 'b')):
version.Version.__init__(self)
# super() is better here, but Version is an old-style class
self.sep = sep
self.prerel_tags = dict(zip(prerel_tags, xrange(len(prerel_tags))))
self.version_re = self._compile_pattern(sep, self.prerel_tags.keys())
self.sep_re = re.compile(re.escape(sep))
if vstring:
self.parse(vstring)
_re_prerel_tag = 'rel_tag'
_re_prerel_num = 'tag_num'
def _compile_pattern(self, sep, prerel_tags):
sep = re.escape(sep)
tags = '|'.join(re.escape(tag) for tag in prerel_tags)
if tags:
release_re = '(?:(?P<{tn}>{tags})(?P<{nn}>\d+))?'\
.format(tags=tags, tn=self._re_prerel_tag, nn=self._re_prerel_num)
else:
release_re = ''
return re.compile(r'^(\d+)(?:{sep}(\d+))*{rel}$'\
.format(sep=sep, rel=release_re))
def parse(self, vstring):
m = self.version_re.match(vstring)
if not m:
raise ValueError("invalid version number '{}'".format(vstring))
tag = m.group(self._re_prerel_tag)
tag_num = m.group(self._re_prerel_num)
if tag is not None and tag_num is not None:
self.prerelease = (tag, int(tag_num))
vnum_string = vstring[:-(len(tag) + len(tag_num))]
else:
self.prerelease = None
vnum_string = vstring
self.version = tuple(map(int, self.sep_re.split(vnum_string)))
def __repr__(self):
return "{cls} ('{vstring}', '{sep}', {prerel_tags})"\
.format(cls=self.__class__.__name__, vstring=str(self),
sep=self.sep, prerel_tags = list(self.prerel_tags.keys()))
def __str__(self):
s = self.sep.join(map(str,self.version))
if self.prerelease:
return s + "{}{}".format(*self.prerelease)
else:
return s
def __lt__(self, other):
"""
Fails when the separator is not the same or when the pre-release tags
are not the same or do not respect the same order.
"""
# TODO deal with trailing zeroes: e.g. "1.2.0" == "1.2"
if self.prerel_tags != other.prerel_tags or self.sep != other.sep:
raise ValueError("Unable to compare: instances have different"
" structures")
if self.version == other.version and self.prerelease is not None and\
other.prerelease is not None:
tag_index = self.prerel_tags[self.prerelease[0]]
other_index = self.prerel_tags[other.prerelease[0]]
if tag_index == other_index:
return self.prerelease[1] < other.prerelease[1]
return tag_index < other_index
elif self.version == other.version:
return self.prerelease is not None and other.prerelease is None
return self.version < other.version
def __eq__(self, other):
tag_index = self.prerel_tags[self.prerelease[0]]
other_index = other.prerel_tags[other.prerelease[0]]
return self.prerel_tags == other.prerel_tags and self.sep == other.sep\
and self.version == other.version and tag_index == other_index and\
self.prerelease[1] == other.prerelease[1]
import unittest
class TestNumberedVersion(unittest.TestCase):
def setUp(self):
self.v = NumberedVersion()
def test_compile_pattern(self):
p = self.v._compile_pattern('.', ['a', 'b'])
tests = {'1.2.3': True, '1a0': True, '1': True, '1.2.3.4a5': True,
'b': False, '1c0': False, ' 1': False, '': False}
for test, result in tests.iteritems():
self.assertEqual(result, p.match(test) is not None, \
"test: {} result: {}".format(test, result))
def test_parse(self):
tests = {"1.2.3.4a5": ((1, 2, 3, 4), ('a', 5))}
for test, result in tests.iteritems():
self.v.parse(test)
self.assertEqual(result, (self.v.version, self.v.prerelease))
def test_str(self):
tests = (('1.2.3',), ('10-2-42rc12', '-', ['rc']))
for t in tests:
self.assertEqual(t[0], str(NumberedVersion(*t)))
def test_repr(self):
v = NumberedVersion('1,2,3rc4', ',', ['lol', 'rc'])
expected = "NumberedVersion ('1,2,3rc4', ',', ['lol', 'rc'])"
self.assertEqual(expected, repr(v))
def test_order(self):
test = ["1.7.0", "1.7.0rc0", "1.11.0"]
expected = ['1.7.0rc0', '1.7.0', '1.11.0']
versions = [NumberedVersion(v, '.', ['rc']) for v in test]
self.assertEqual(expected, list(map(str,sorted(versions))))
if __name__ == '__main__':
unittest.main()
所以,它可以像这样使用:
import version2
versions = ["1.7.0", "1.7.0rc2", "1.7.0rc1", "1.7.1", "1.11.0"]
sorted(versions, key=lambda v: version2.NumberedVersion(v, '.', ['rc']))
输出:
['1.7.0rc1', '1.7.0rc2', '1.7.0', '1.7.1', '1.11.0']
因此,总之,使用python包含的电池或推出自己的电池。
关于这个实现:可以通过处理发行版中的尾随零来改进它,并记住正则表达式的编译。
答案 2 :(得分:1)
我用这个:
#!/usr/bin/python
import re
def sort_software_versions(versions = [], reverse = False):
def split_version(version):
def toint(x):
try:
return int(x)
except:
return x
return map(toint, re.sub(r'([a-z])([0-9])', r'\1.\2', re.sub(r'([0-9])([a-z])', r'\1.\2', version.lower().replace('-', '.'))).split('.'))
def compare_version_list(l1, l2):
def compare_version(v1, v2):
if isinstance(v1, int):
if isinstance(v2, int):
return v1 - v2
else:
return 1
else:
if isinstance(v2, int):
return -1
else:
return cmp(v1, v2)
ret = 0
n1 = len(l1)
n2 = len(l2)
if n1 < n2:
l1.extend([0]*(n2 - n1))
if n2 < n1:
l2.extend([0]*(n1 - n2))
n = max(n1, n2)
i = 0
while not ret and i < n:
ret = compare_version(l1[i], l2[i])
i += 1
return ret
return sorted(versions, cmp = compare_version_list, key = split_version, reverse = reverse)
print(sort_software_versions(['1.7.0', '1.7.0.rc0', '1.8.0']))
['1.7.0.rc0', '1.7.0', '1.8.0']
这样它可以正确处理alpha,beta,rc。它可以处理包含连字符的版本,或者当人们将'rc'粘贴到版本时。 re.sub可以使用编译的regexp,但这样就可以了。
答案 3 :(得分:1)
我使用pkg_resources模块如下:
from pkg_resources import parse_version
def test_version_sorting():
expected = ['1.0.0dev0',
'1.0.0dev1',
'1.0.0dev2',
'1.0.0dev10',
'1.0.0rc0',
'1.0.0rc2',
'1.0.0rc5',
'1.0.0rc21',
'1.0.0',
'1.1.0',
'1.1.1',
'1.1.11',
'1.2.0',
'1.3.0',
'1.23.0',
'2.0.0', ]
alphabetical = sorted(expected)
shuffled = sorted(expected, key=lambda x: random())
assert expected == sorted(alphabetical, key=parse_version)
assert expected == sorted(shuffled, key=parse_version)
请注意,从预期版本列表创建随机排序会使这成为一个可能不稳定的单元测试,因为两次运行将不会具有相同的数据。不过,在这种情况下,它应该无关紧要......希望如此。
答案 4 :(得分:1)
我发现这很有帮助,并且更简单:
from packaging import version
vers = ["1.7.0", "1.7.0rc2", "1.7.0rc1", "1.7.1", "1.11.0"]
sorted(vers, key=lambda x: version.Version(x))
这将导致:
['1.7.0rc1', '1.7.0rc2', '1.7.0', '1.7.1', '1.11.0']
添加reverse=True
会将它们按“降序”排列,这对我有帮助。
['1.11.0', '1.7.1', '1.7.0', '1.7.0rc2', '1.7.0rc1']
它可以对各种各样的版本号进行排序(我的测试平台是Linux版本v4.11.16等)
答案 5 :(得分:0)
就我而言,我想使用“ .devX”作为“预发行”标识符,因此这是另一种实现方式,主要基于distutils.version.StrictVersion
class ArtefactVersion(Version):
"""
Based on distutils/version.py:StrictVersion
"""
version_re = re.compile(r'^(\d+) \. (\d+) \. (\d+) (\.dev\d+)?$', re.VERBOSE | re.ASCII)
def parse(self, vstring):
match = self.version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease) = match.group(1, 2, 3, 4)
self.version = tuple(map(int, [major, minor, patch]))
if prerelease:
self.prerelease = prerelease[4:]
else:
self.prerelease = None
def __str__(self):
vstring = '.'.join(map(str, self.version))
if self.prerelease:
vstring = vstring + f".dev{str(self.prerelease)}"
return vstring
def _cmp(self, other):
if isinstance(other, str):
other = ArtefactVersion(other)
if self.version != other.version:
# numeric versions don't match
# prerelease stuff doesn't matter
if self.version < other.version:
return -1
else:
return 1
# have to compare prerelease
# case 1: neither has prerelease; they're equal
# case 2: self has prerelease, other doesn't; other is greater
# case 3: self doesn't have prerelease, other does: self is greater
# case 4: both have prerelease: must compare them!
if (not self.prerelease and not other.prerelease):
return 0
elif (self.prerelease and not other.prerelease):
return -1
elif (not self.prerelease and other.prerelease):
return 1
elif (self.prerelease and other.prerelease):
if self.prerelease == other.prerelease:
return 0
elif self.prerelease < other.prerelease:
return -1
else:
return 1
else:
assert False, "never get here"