Comment puis-je comparer les numéros de version en Python?

je suis en train de parcourir un répertoire qui contient des oeufs pour ajouter ces oeufs au sys.path . Si il y a deux versions de la même chose .l'oeuf dans le répertoire, je tiens à ajouter que le dernier.

j'ai une expression régulière r"^(?P<eggName>w+)-(?P<eggVersion>[d.]+)-.+.egg$ pour extraire le nom et la version du nom de fichier. Le problème est de comparer le numéro de version, qui est une chaîne comme 2.3.1 .

puisque je compare les cordes, 2 sortes au-dessus de 10, mais ce n'est pas correct pour les versions.

>>> "2.3.1" > "10.1.1"
True

je pourrais faire quelques fentes, parsing, casting à int, etc., et je finirais par avoir un contournement. Mais C'est Python, pas Java . Y a-t-il une façon élégante de comparer les chaînes de version?

146
demandé sur Steven Vascellaro 2012-08-09 20:22:50

8 réponses

utiliser distutils.version ou 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

les Différences entre les deux options:

  • distutils.version est construit dans mais n'est pas documenté et conforme seulement à la remplacé PEP 386 ;
  • packaging.version.parse est un utilitaire tiers mais est utilisé par setuptools (donc vous probablement déjà il est conforme à l'actuel PEP 440 ; il gère également les versions" lâches "et" strictes "dans une seule fonction (bien que les versions" anciennes " seront toujours triées avant les versions valides).

comme distutils.version n'est pas documenté, voici les docstrings pertinents (basés sur Python 3.3) pour référence (nicked from the source ):

Chaque classe de numéro de version implémente l'interface suivante:

  • la méthode 'parse' prend une chaîne et la analyse à un certain interne représentation; si la chaîne est un numéro de version invalide, "parse" soulève une ValueError exception
  • le constructeur de classe prend un argument de chaîne optionnel qui, s'il est fourni, est transmis à 'parse'
  • __str__ reconstruit la chaîne qui a été passée à 'analyser' (ou une chaîne équivalente -- ie. un qui générera un équivalent numéro de version de l'instance)
  • __repr__ génère du code Python pour recréer le numéro de version instance
  • _cmp compare l'instance courante avec une autre instance de la même classe ou d'une chaîne (qui sera attribuée à une instance de la même classe, doit donc suivre les mêmes règles)

StrictVersion

de la numérotation de Version pour anal retentives et logiciel idéalistes. Implémente l'interface standard pour les classes de nombre de version décrit ci-dessus. Un numéro de version se compose de deux ou trois composants numériques séparés par des points, avec une étiquette facultative "pré-release" sur la fin. L'étiquette de prélibération se compose de la lettre " a " ou "b" suivi par un nombre. Si les composantes numériques de deux versions nombres sont égaux, alors avec une version pré-balise de toujours être considéré plus tôt (moins) qu'un sans.

les numéros de version suivants sont valables (dans l'ordre suivant: serait obtenue par tri selon la fonction cmp fournie):

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

voici des exemples de numéros de version invalides:

1
2.7.2.2
1.3.a4
1.3pl1
1.3c4

la raison d'être de ce système de numérotation de version sera expliquée dans le distutils.


LooseVersion

numérotation de Version pour les anarchistes et les réalisateurs de logiciels. Implémente l'interface standard pour les classes de nombre de version décrit ci-dessus. Un numéro de version se compose d'une série de nombres, séparés par des périodes ou des chaînes de lettres. Lors de la comparaison numéros de version, les composantes numériques seront comparées numériquement, et les composants alphabétiques lexiquement. La suite sont tous des numéros de version valides, dans aucun ordre particulier:

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

en fait, il n'y a pas de numéro de version invalide sous ce schéma; les règles sont simples et prévisibles, mais ne donne pas toujours les résultats que vous voulez (pour une définition de "vouloir").

229
répondu ecatmur 2016-07-27 09:59:29

setuptools définit parse_version() . Cela implémente PEP 0440 -- Identification de Version et est également capable d'analyser les versions qui ne suivent pas la PEP. Cette fonction est utilisée par easy_install et pip pour manipuler la comparaison de version. Du docs :

a analysé la chaîne de version d'un projet telle que définie par PEP 440. La valeur retournée sera un objet qui représente la version. Ces objets peuvent être comparés les uns aux autres et triés. L'algorithme de tri est tel que défini par PEP 440 avec l'ajout que toute version qui n'est pas une version valide de PEP 440 sera considérée moins que n'importe quelle version valide de PEP 440 et les versions invalides continueront à trier en utilisant l'algorithme original.

l '"algorithme original" référencé a été défini dans des versions plus anciennes du docs, avant L'existence de PEP 440.

sémantiquement, le format est un croisement approximatif entre les classes StrictVersion et LooseVersion de distutils; si vous lui donnez des versions qui fonctionneraient avec StrictVersion , alors ils compareront de la même manière. Autrement, les comparaisons sont plus comme une forme" plus intelligente "de LooseVersion . Il est possible de créer des schémas de codage de version pathologique qui tromperont cet analyseur, mais ils devraient être très rares dans la pratique.

Le la documentation fournit quelques exemples:

si vous voulez être certain que le système de numérotation que vous avez choisi fonctionne comme vous le pensez, vous pouvez utiliser le pkg_resources.parse_version() fonction pour comparer différents numéros de 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

si vous n'utilisez pas d'outils setuptools, le projet packaging divise cette fonctionnalité et d'autres liées à l'emballage dans une bibliothèque séparée.

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

from pkg_resources import parse_version
parse_version('1.0.3.dev')
72
répondu davidism 2018-07-30 13:39:04
def versiontuple(v):
    return tuple(map(int, (v.split("."))))

>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False
46
répondu kindall 2012-08-09 16:26:40

qu'y a-t-il de mal à transformer la chaîne de version en un tuple et à partir de là? Semble assez élégant pour moi

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

la solution de @kindall est un exemple rapide de la qualité du code.

7
répondu Gabi Purcaru 2012-08-09 16:26:27

il y a packaging package disponible, qui vous permettra de comparer les versions selon PEP-440 , ainsi que les versions anciennes.

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

support de la version Legacy:

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

la Comparaison de l'héritage de la version avec PEP-440 version.

>>> LegacyVersion('1.2.8.5-5-gdeadbeef') < Version('1.2.8.6')
True
7
répondu sashk 2015-02-12 18:38:29

vous pouvez utiliser le paquet semver pour déterminer si une version satisfait à l'exigence version sémantique . Ce n'est pas la même chose que de comparer deux versions réelles, mais c'est un type de comparaison.

par exemple, la version 3.6.0+1234 devrait être la même que la version 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
2
répondu Prikkeldraad 2018-07-30 13:34:10

Affiche ma pleine fonction basée sur la solution de Kindall. J'ai été capable de supporter tous les caractères alphanumériques mélangés avec les nombres en remplaçant chaque section de version par des zéros de tête.

bien que certainement pas aussi jolie que sa fonction one-liner, il semble bien fonctionner avec les numéros de version alphanumérique. (Assurez-vous simplement de définir la valeur zfill(#) de manière appropriée si vous avez de longues chaînes dans votre système de versioning.)

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
1
répondu Phaxmohdem 2015-02-17 20:04:52

je vais aller plus pour l'option touple, faire un test, en utilisant LooseVersion, Je ne reçois dans mon test le deuxième plus grand, (pourrait faire quelque chose de mal puisque c'est ma première fois en utilisant cette bibliothèque)

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)

et voilà ce que j'ai eu:

3.4.5 avec vrac

(10, 5, 2) et avec les perruques

-3
répondu pelos 2017-02-08 17:22:30