我正在浏览一个包含卵的目录,以便将这些卵添加到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,不是Java。是否有一种优雅的方法来比较版本字符串?


当前回答

这是一个用于比较三个版本号的紧凑代码。注意,这里的字符串比较对所有对都失败了。

from itertools import permutations

for v1, v2 in permutations(["3.10.21", "3.10.3", "3.9.9"], 2):
    print(f"\nv1 = {v1}, v2 = {v2}")
    print(f"v1 < v2      version.parse(v1) < version.parse(v2)")
    print(f"{v1 < v2}         {version.parse(v1) < version.parse(v2)}")

这给了我们:

v1='3.10.21', v2='3.10.3'
v1 < v2      version.parse(v1) < version.parse(v2)
True         False

v1='3.10.21', v2='3.9.9'
v1 < v2      version.parse(v1) < version.parse(v2)
True         False

v1='3.10.3', v2='3.10.21'
v1 < v2      version.parse(v1) < version.parse(v2)
False         True

v1='3.10.3', v2='3.9.9'
v1 < v2      version.parse(v1) < version.parse(v2)
True         False

v1='3.9.9', v2='3.10.21'
v1 < v2      version.parse(v1) < version.parse(v2)
False         True

v1='3.9.9', v2='3.10.3'
v1 < v2      version.parse(v1) < version.parse(v2)
False         True

Permutations (iterable, 2)给出了一个可迭代对象的所有长度为2的排列。例如,

list(permutations('ABC', 2))

给了我们[(A, B), (' A ', ' C '), (A, B),(“B”、“C”)(“C”,“A”),(“C”、“B”)]。

其他回答

使用python增加版本

def increment_version(version):
    version = version.split('.')
    if int(version[len(version) - 1]) >= 99:
        version[len(version) - 1] = '0'
        version[len(version) - 2] = str(int(version[len(version) - 2]) + 1)
    else:
        version[len(version) - 1] = str(int(version[len(version) - 1]) + 1)
    return '.'.join(version)

version = "1.0.0"
version_type_2 = "1.0"
print("old version",version ,"new version",increment_version(version))
print("old version",version_type_2 ,"new version",increment_version(version_type_2))

打包库包含用于处理版本和其他打包相关功能的实用程序。它实现了PEP 0440——版本标识,还能够解析不遵循PEP的版本。pip和其他常用的Python工具使用它来提供版本解析和比较。

$ pip install packaging
from packaging.version import parse as parse_version
version = parse_version('1.0.3.dev')

它从setuptools和pkg_resources中的原始代码中分离出来,以提供更轻量级和更快的包。


在打包库存在之前,这个功能在pkg_resources (setuptools提供的一个包)中可以找到(现在仍然可以找到)。然而,这不再是首选,因为setuptools不再保证被安装(存在其他打包工具),具有讽刺意味的是,pkg_resources在导入时使用了相当多的资源。然而,所有的文档和讨论仍然是相关的。

从parse_version()文档:

解析由PEP 440定义的项目版本字符串。返回值将是一个表示版本的对象。这些对象可以相互比较和排序。排序算法是由PEP 440定义的,添加的是,任何不是有效PEP 440版本的版本都将被视为小于任何有效PEP 440版本,无效版本将继续使用原始算法进行排序。

引用的“原始算法”是在PEP 440存在之前的旧版本文档中定义的。

从语义上讲,该格式是distutils的StrictVersion类和LooseVersion类之间的一个粗略的交叉;如果你给它一个与StrictVersion兼容的版本,那么它们会以同样的方式进行比较。否则,比较更像是一种“更聪明”的LooseVersion形式。可以创建病态的版本编码方案来欺骗这个解析器,但在实践中应该非常罕见。

文档提供了一些例子:

如果你想确定你选择的编号方案是否有效 您可以使用pkg_resources.parse_version() 函数比较不同版本号: >>>从pkg_resources导入parse_version > > > parse_version (1.9.a.dev) = = parse_version(“1.9 a0dev”) 真正的 > > > parse_version (2.1 rc2) < parse_version(“2.1”) 真正的 >>> parse_version('0.6a9dev-r41475') < parse_version('0.6a9') 真正的

我正在寻找一个解决方案,不会增加任何新的依赖。检查以下(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()

编辑:添加变量与元组比较。当然,具有元组比较的变体更好,但我正在寻找具有整数比较的变体

使用packaging.version.parse。

>>> # pip install packaging
>>> 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'

package .version.parse是一个第三方实用程序,但被setuptools使用(所以你可能已经安装了它),并且符合当前的PEP 440;如果版本兼容,它将返回package .version. version,如果不兼容,则返回package .version. legacyversion。后者总是在有效版本之前排序。

注意:setuptools最近提供了打包。


您可能遇到的一种古老且现在已弃用的方法是distutils。版本,它是无文档的,只符合取代的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。版本没有文档,这里是相关的文档字符串。

您可以使用semver包来确定一个版本是否满足语义版本需求。这与比较两个实际版本不同,但这是一种比较。

例如,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