如何比较Python中的版本号?

Bor*_*jaX 205 python version string-comparison

我正在走一个包含鸡蛋的目录,将这些鸡蛋添加到鸡蛋中sys.path.如果目录中有相同.egg的两个版本,我想只添加最新版本.

我有一个正则表达式r"^(?P<eggName>\w+)-(?P<eggVersion>[\d\.]+)-.+\.egg$从文件名中提取名称和版本.问题是比较版本号,这是一个字符串2.3.1.

因为我正在比较字符串,2种类型超过10,但这对于版本来说不正确.

>>> "2.3.1" > "10.1.1"
True
Run Code Online (Sandbox Code Playgroud)

我可以做一些拆分,解析,转换为int等,我最终会得到一个解决方法.但这是Python,而不是Java.有比较版本字符串的优雅方法吗?

eca*_*mur 311

使用packaging.version.parse.

>>> 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'
Run Code Online (Sandbox Code Playgroud)

packaging.version.parse是第三方实用程序,但由setuptools使用(因此您可能已安装它)并且符合当前的PEP 440 ; packaging.version.Version如果版本符合要求,它将返回a ,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'
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它将有效的PEP 440版本视为"不严格",因此不符合现代Python关于有效版本的概念.

由于distutils.version没有记载,是相关的文档字符串.

  • 这是一个哭泣的耻辱`distutils.version`没有记录. (11认同)
  • 看起来NormalizedVersion将不会出现,因为它已被取代,因此不再弃用LooseVersion和StrictVersion. (2认同)
  • 不能信任imho`packaging.version.parse`来比较版本。例如尝试`parse('1.0.1-beta.1')&gt; parse('1.0.0')`。 (2认同)
  • 在 Python 3.6+ 中: `from pkg_resources import parking` 然后 `packaging.version.parse("0.1.1rc1") &lt;package.version.parse("0.1.1rc2")` (2认同)

dav*_*ism 96

setuptools定义parse_version().这实现了PEP 0440 - 版本标识,并且还能够解析不遵循PEP的版本.该功能使用easy_installpip处理的版本比较.来自文档:

解析了PEP 440定义的项目版本字符串.返回的值将是表示版本的对象.可以将这些对象彼此进行比较并进行排序.排序算法如PEP 440所定义,并且添加任何不是有效PEP 440版本的版本将被认为小于任何有效PEP 440版本,并且无效版本将使用原始算法继续排序.

在PEP 440存在之前,引用的"原始算法"在较旧版本的文档中定义.

在语义上,格式是distutils StrictVersionLooseVersion类之间的粗略交叉; 如果你给它使用的版本StrictVersion,那么他们将以相同的方式进行比较.否则,比较更像是一种"更聪明"的形式LooseVersion.有可能创建会欺骗这个解析器的病态版本编码方案,但它们在实践中应该是非常罕见的.

文档提供了一些示例:

如果您想确定所选的编号方案是否按您认为的方式工作,您可以使用该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
Run Code Online (Sandbox Code Playgroud)

如果您不使用setuptools,则打包项目会将此包装相关功能和其他与包装相关的功能拆分为单独的库.

from packaging import version
version.parse('1.0.3.dev')

from pkg_resources import parse_version
parse_version('1.0.3.dev')
Run Code Online (Sandbox Code Playgroud)


kin*_*all 54

def versiontuple(v):
    return tuple(map(int, (v.split("."))))

>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False
Run Code Online (Sandbox Code Playgroud)

  • **不,这应该是不被接受的答案.**谢天谢地,事实并非如此.在一般情况下,版本说明符的可靠解析是非平凡的(如果不是实际上不可行).不要重新发明轮子,然后继续打破它.正如[ecatmur](/sf/users/39710471/)建议[上面](/sf/answers/832151981/),只需使用`distutils.version.LooseVersion`.这就是它的用途. (12认同)
  • 其他答案在标准库中,并遵循PEP标准. (10认同)
  • 这将失败,例如`versiontuple("1.0")> versiontuple("1")`.版本是相同的,但元组创建了`(1,)!=(1,0)` (6认同)
  • @chris 在打包应用程序时,其他答案要求您添加所有 distutils 或所有打包和 pkg_resources ...这有点臃肿。这是一个有用的答案,在大多数情况下都有效 - 并且不会导致包膨胀。这确实取决于上下文。 (5认同)
  • 从什么意义上说版本1和版本1.0相同?版本号不是浮点数。 (3认同)

Gab*_*aru 11

将版本字符串转换为元组并从那里转换有什么问题?对我来说似乎很优雅

>>> (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
Run Code Online (Sandbox Code Playgroud)

@ kindall的解决方案是代码看起来有多好的一个简单例子.

  • 它非常适合确保 `sys.version_info &gt; (3, 6)` 或其他。 (3认同)
  • 我认为可以通过提供将 __PEP440__ 字符串转换为元组的代码来扩展这个答案。我想你会发现这不是一项微不足道的任务。我认为最好留给为“setuptools”执行翻译的包,即“pkg_resources”。 (2认同)

小智 9

这样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
Run Code Online (Sandbox Code Playgroud)

  • @Jed我不认为“setuptools”依赖于“packaging”。我可以导入“setuptools”和“pkg_resources”,但“导入打包”会引发导入错误。 (2认同)

sas*_*shk 7

有可用的包装包,可以根据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
Run Code Online (Sandbox Code Playgroud)

旧版本支持:

>>> LegacyVersion('1.2.8.5-5-gdeadbeef')
<LegacyVersion('1.2.8.5-5-gdeadbeef')>
Run Code Online (Sandbox Code Playgroud)

将旧版本与PEP-440版本进行比较.

>>> LegacyVersion('1.2.8.5-5-gdeadbeef') < Version('1.2.8.6')
True
Run Code Online (Sandbox Code Playgroud)

  • 对于那些想知道`packaging.version.Version`和`packaging.version.parse`之间区别的人:"[`version.parse`]需要一个版本字符串,如果版本是有效的,它会将其解析为`Version` PEP 440版本,否则它会将其解析为`LegacyVersion`." (而`version.Version`会引发`InvalidVersion`; [source](https://packaging.pypa.io/en/latest/version/#packaging.version.parse)) (2认同)

Pha*_*dem 5

根据 Kindall 的解决方案发布我的完整功能。通过用前导零填充每个版本部分,我能够支持与数字混合的任何字母数字字符。

虽然肯定不如他的单行函数那么漂亮,但它似乎适用于字母数字版本号。(zfill(#)如果您的版本控制系统中有长字符串,请务必正确设置该值。)

def versiontuple(v):
   filled = []
   for point in v.split("."):
      filled.append(point.zfill(8))
   return tuple(filled)
Run Code Online (Sandbox Code Playgroud)

.

>>> versiontuple("10a.4.5.23-alpha") > versiontuple("2a.4.5.23-alpha")
True


>>> "10a.4.5.23-alpha" > "2a.4.5.23-alpha"
False
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以使用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
Run Code Online (Sandbox Code Playgroud)