18

バージョンを並べ替える場合と同様に、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')]
4

6 に答える 6

21
>>> 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]
于 2012-09-04T01:04:36.370 に答える
15

主な編集:古い答えはあまりにも不自然でした。ここに 2 つのきれいなソリューションがあります。

そのため、現在、希望する順序を達成する方法として、実際のリリースの前に候補 "rc" をリリースする方法が 3 つあります。

  1. 私の昔ながらの命令型の順序付け
  2. StrictVersion同じパッケージのを使用するには、「rc」の代わりに「b」を使用します
  3. クラスを拡張してVersion、任意のタグとタグ順序のサポートを追加します

1. 古い命令型の順序付け

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.「rc」の代わりに「b」を使用する

パッケージdistutils.versionには別のクラスもあり、アルファ版またはベータ版のリリースとして記述または通知することが許可されているStrictVersion場合は、このクラスがその役割を果たします。1.7.0.rc01.7.0a01.7.0b0

あれは:

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モジュールを使用して、ある形式から別の形式への変換を行うことができます。

3. Version クラスを拡張する

前のソリューションの明らかな問題は、 の柔軟性の欠如ですStrictVersion。またはの代わりにversion_re使用するように class 属性を変更すると、それが受け入れられたとしても、 (python 2.7.3 以降) として出力されます。rcab1.7.1rc01.7.1r0

独自のカスタム バージョン クラスを実装することで、これを正しく行うことができます。これは、少なくとも場合によっては正確性を確保するためのいくつかの単体テストを使用して、次のように実行できます。

#!/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 に付属のバッテリーを使用するか、独自のバッテリーを展開します。

この実装について: リリースで末尾のゼロを処理し、正規表現のコンパイルをメモすることで改善される可能性があります。

于 2012-09-04T01:00:58.957 に答える
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 はコンパイルされた正規表現を使用できますが、これで十分に機能します。

于 2014-03-04T08:15:20.503 に答える