Как сравнить номера версий в 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