用Python排序版本 [英] Sort Versions in Python

查看:61
本文介绍了用Python排序版本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试获取它,以便1.7.0在1.7.0.rc0之后但在1.8.0之前,如果要对版本进行排序的话应该如此.我认为LooseVersion的全部目的是它正确地处理了这种事情的排序和比较.

I'm trying to get it so that 1.7.0 comes after 1.7.0.rc0 but before 1.8.0, as it should if you were sorting versions. I thought the whole point of LooseVersion was that it handled the sorting and comparison of this kind of thing correctly.

>>> 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')]

推荐答案

主要旧答案太含糊了.这是两个更漂亮的解决方案.

MAJOR old answer was too unpythonic. Here are two prettier solutions.

因此,我目前看到了三种实现所需排序的方法,即在实际发布之前发布候选文件"rc".

So, I currently see about three ways of achieving the wished ordering, releases candidates "rc" before actual releases.

  1. 我古老的命令式订购
  2. 使用"b"代替"rc",以便使用同一程序包中的 StrictVersion
  3. 扩展 Version 类以添加对任意标记和标记顺序的支持
  1. my old, imperative-style ordering
  2. use "b" instead of "rc" in order to use StrictVersion, from the same package
  3. extend the Version class to add support for arbitrary tags and tag ordering

1.古老的命令式排序

从distutils.version中的

1. Old, imperative-style ordering

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']

2.使用"b"代替"rc"

如果允许您的 1.7.0.rc0 进行操作,则 distutils.version 包还具有另一个类 StrictVersion 可以完成此任务.记为 1.7.0a0 1.7.0b0 ,并注明了alpha或beta版本.

2. Use "b" instead of "rc"

The package distutils.version also has another class, StrictVersion which does the job, if your 1.7.0.rc0 is allowed to be written as 1.7.0a0 or 1.7.0b0 noting alpha or beta releases.

也就是说:

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 完成从一种形式到另一种形式的翻译模块.

Translation from one form to another can be done using the re module.

先前解决方案的明显问题是 StrictVersion 缺乏灵活性.更改 version_re 类属性以使用 rc 代替 a b ,即使它接受 1.7.1rc0 ,仍将其打印为 1.7.1r0 (自python 2.7.3起).

The obvious problem of the previous solution is the lack of flexibility of StrictVersion. Altering the version_re class attribute to use rc instead of a or b, even if it accepts 1.7.1rc0, still prints it as 1.7.1r0 (as of python 2.7.3).

我们可以通过实现自己的自定义版本类来解决问题.可以这样进行,通过一些单元测试至少在某些情况下可以确保正确性:

We can get it right by implementing our own custom version class. This can be done like this, with some unit tests to ensure correctness at least in some cases:

#!/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()

因此,它可以像这样使用:

So, it can be used like this:

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随附的电池或自行推出电池.

So, in conclusion, use python's included batteries or roll out your own.

关于此实现:可以通过处理发行版中的尾随零并记住正则表达式的编译来改进它.

About this implementation: it could be improved by dealing with the trailing zeroes in the releases, and memoize the compilation of the regular expressions.

这篇关于用Python排序版本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆