Как сравнить номера версий в Python?

Я иду каталог, который содержит яйца, чтобы добавить эти яйца в sys.path. Если есть две версии одного и того же .яйцо в каталоге, я хочу добавить только последнее.

у меня есть регулярное выражение r"^(?P<eggName>w+)-(?P<eggVersion>[d.]+)-.+.egg$ чтобы извлечь имя и версию из имени файла. Проблема заключается в сравнении номера версии, который является строкой типа 2.3.1.

так как я сравниваю строки, 2 сорта выше 10, но это неправильно для версии.

>>> "2.3.1" > "10.1.1"
True

Я мог бы сделать некоторое разделение, разбор, кастинг на int и т. д. и в конце концов я бы нашел обходной путь. Но это питон,не Java. Есть ли элегантный способ сравнения строк версий?

8 ответов


использовать distutils.version или packaging.version.parse.

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

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

различия между двумя вариантами:

  • distutils.version встроен, но недокументирован и соответствует только замененному PEP 386;
  • packaging.version.parse является сторонней утилитой, но используется setuptools (так что вы, вероятно, уже установили его) и соответствует текущему PEP 440; он также обрабатывает "свободные" и "строгие" версии в одной функции (хотя "устаревшие" версии всегда сортируются перед допустимыми версиями).

As distutils.version является недокументированным, вот соответствующие docstrings (на основе Python 3.3) для справки (nicked from источник):

каждый класс номера версии реализует следующий интерфейс:

  • метод 'parse' принимает строку и анализирует ее на некоторую внутреннюю представление; если строка является недопустимым номером версии, 'parse' поднимает ValueError исключение
  • конструктор класса принимает необязательный строковый аргумент, который, если задано, передается в 'parse'
  • __str__ восстанавливает строку, которая была передана в 'parse' (или эквивалентная строка -- ie. тот, который будет генерировать эквивалент номер версии экземпляра)
  • __repr__ генерирует код Python для воссоздания номера версии пример
  • _cmp сравнивает текущий экземпляр с другим экземпляром того же класса или строки (которая будет проанализирована для экземпляра того же класса, таким образом, должны следовать тем же правилам)

StrictVersion

нумерация версий для анальных ретентивов и идеалистов программного обеспечения. Реализует стандартный интерфейс для классов номеров версий как описанный выше. Номер версии состоит из двух или три числовые компоненты, разделенные точками, с дополнительным тегом " pre-release на конец. Тег предварительного выпуска состоит из буквы " a " или "b" за ним следовал номер. Если числовые компоненты двух версий числа равны, то один с тегом pre-release будет всегда считается более ранним (меньшим), чем один без.

следующие действительные номера версии (показанные в заказе который было бы получено путем сортировать согласно поставленному cmp функция):

0.4       0.4.0  (these two are equivalent)
0.4.1
0.5a1
0.5b3
0.5
0.9.6
1.0
1.0.4a3
1.0.4b1
1.0.4

ниже приведены примеры недопустимых номеров версий:

1
2.7.2.2
1.3.a4
1.3pl1
1.3c4

будет объяснено обоснование этой системы нумерации версий в документации distutils.


LooseVersion

нумерация версий для анархистов и программных реалистов. Реализует стандартный интерфейс для классов номеров версий как описанный выше. Номер версии состоит из серии числа, разделенные либо точками, либо строками букв. При сравнении номера версий, числовые компоненты будут сравнены численно, а алфавитные компоненты лексически. Следующий все действительные номера версий, в определенном порядке:

1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0

в самом деле, нет такой вещи, как неверный номер версии эта схема; правила для сравнения просты и предсказуемы, но не всегда может дать результаты, которые вы хотите (для некоторых определение "хочу").


setuptools определяет parse_version(). Это реализует PEP 0440 -- идентификация версии а также способен анализировать версии, которые не следуют за ОПТОСОЗ. Эта функция используется easy_install и pip для обработки сравнения версий. От docs:

проанализировал строку версии проекта, определенную PEP 440. Возвращаемое значение будет объектом, представляющим версию. Эти объекты можно сравнить друг с другом и рассортировали. Алгоритм сортировки определяется PEP 440 с добавлением, что любая версия, которая не является допустимой версией PEP 440, будет считаться меньшей, чем любая допустимая версия PEP 440, и недопустимые версии будут продолжать сортировку с использованием исходного алгоритма.

" исходный алгоритм", на который ссылаются, был определен в более старых версиях документов до того, как PEP 440 существовал.

семантически формат представляет собой грубый крест между distutils'StrictVersion и LooseVersion классы; если вы дадите его версии, которые будут работать с 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

если вы не используете setuptools, то упаковка проект разбивает эту и другие функции, связанные с упаковкой, на отдельную библиотеку.

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

from pkg_resources import parse_version
parse_version('1.0.3.dev')

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

>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False

что плохого в преобразовании строки версии в кортеж и переходе оттуда? Кажется, достаточно элегантно для меня

>>> (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
@kindall является быстрым примером того, насколько хорошо будет выглядеть код.

здесь упаковка пакет доступен, что позволит вам сравнить версии в соответствии с 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

старая версия поддержка:

>>> LegacyVersion('1.2.8.5-5-gdeadbeef')
<LegacyVersion('1.2.8.5-5-gdeadbeef')>

сравнение устаревшей версии с версией PEP-440.

>>> LegacyVersion('1.2.8.5-5-gdeadbeef') < Version('1.2.8.6')
True

можно использовать 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

публикация моей полной функции на основе решения Kindall. Я смог поддерживать любые буквенно-цифровые символы, смешанные с числами, заполняя каждый раздел версии начальными нулями.

хотя, конечно, не так красиво, как его однострочная функция, она, похоже, хорошо работает с Альфа-числовыми номерами версий. (Просто не забудьте установить zfill(#) значение соответственно, если у вас есть длинные строки в вашей системе управления версиями.)

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

.

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


>>> "10a.4.5.23-alpha" > "2a.4.5.23-alpha"
False

Я пойду больше для опции touple, делая тест, используя LooseVersion, я получаю в своем тесте второй по величине (возможно, делаю что-то wront, так как это мой первый раз, используя эту библиотеку)

import itertools
from distutils.version import LooseVersion, StrictVersion

lista_de_frameworks = ["1.1.1", "1.2.5", "10.5.2", "3.4.5"]

for a, b in itertools.combinations(lista_de_frameworks, 2):
    if LooseVersion(a) < LooseVersion(b):
        big = b
print big

list_test = []
for a in lista_de_frameworks:
    list_test.append( tuple(map(int, (a.split(".")))))

print max(list_test)

и вот что я получил:

3.4.5 с Loose

(10, 5, 2) и с touples