python version string-comparison

¿Cómo comparo los números de versión en Python?



version string-comparison (8)

¿Qué hay de malo con la transformación de la cadena de versiones en una tupla y desde allí? Parece lo suficientemente elegante para mí

>>> (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 solución de @ kindall es un ejemplo rápido de lo bueno que se vería el código.

Estoy caminando por un directorio que contiene huevos para agregar esos huevos a sys.path . Si hay dos versiones del mismo .egg en el directorio, quiero agregar solo la última.

Tengo una expresión regular r"^(?P<eggName>/w+)-(?P<eggVersion>[/d/.]+)-.+/.egg$ para extraer el nombre y la versión del nombre del archivo. problema es comparar el número de versión, que es una cadena como 2.3.1 .

Como estoy comparando cadenas, 2 ordena por encima de 10, pero eso no es correcto para las versiones.

>>> "2.3.1" > "10.1.1" True

Podría hacer algunas divisiones, análisis, conversión a int, etc., y eventualmente obtendría una solución alternativa. Pero esto es Python, no Java . ¿Hay alguna manera elegante de comparar cadenas de versión?


Después de leer sobre la numeración de la versión semántica en semver.org. Intenté comparar con un símbolo +.

la versión 3.6.0 + 1234 debería ser igual a 3.6.0. Solo el paquete de pip semver dio los resultados correctos (python 2.7):

import semver print semver.match(''3.6.0+1234'', ''==3.6.0'') # True from packaging.version import Version, LegacyVersion print LegacyVersion(''3.6.0+1234'') == LegacyVersion(''3.6.0'') # False from pkg_resources import parse_version print parse_version(''3.6.0+1234'') == parse_version(''3.6.0'') # False from distutils.version import LooseVersion, StrictVersion print LooseVersion(''3.6.0+1234'') == LooseVersion(''3.6.0'') # False


Hay un paquete de packaging disponible, que le permitirá comparar versiones según PEP-440 , así como versiones heredadas.

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

Soporte de versión heredada:

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

Comparando la versión heredada con la versión PEP-440.

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


Publicando mi función completa basada en la solución de Kindall. Pude soportar cualquier carácter alfanumérico mezclado con los números rellenando cada sección de versión con ceros a la izquierda.

Aunque ciertamente no es tan bonito como su función de una sola línea, parece funcionar bien con los números de versión alfanuméricos. (Solo asegúrese de establecer el valor zfill(#) apropiada si tiene cadenas largas en su sistema de control de versiones).

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


Utilice distutils.version o 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

Diferencias entre las dos opciones:

  • distutils.version está integrado, pero no está documentado y es conforme solo al PEP 386 reemplazado;
  • packaging.version.parse es una utilidad de terceros pero es utilizada por setuptools (por lo que probablemente ya la tengas instalada) y cumple con la PEP 440 actual; también maneja versiones "sueltas" y "estrictas" en una sola función (aunque las versiones "heredadas" siempre ordenarán antes de las versiones válidas).

Como distutils.version no está documentado, aquí están las cadenas de documentos relevantes (basadas en Python 3.3) para referencia (recortadas desde la fuente ):

Cada clase de número de versión implementa la siguiente interfaz:

  • el método ''analizar'' toma una cadena y la analiza en representación interna; si la cadena es un número de versión no válido, ''parse'' genera una excepción ValueError
  • el constructor de clase toma un argumento de cadena opcional que, si se proporciona, se pasa a ''analizar''
  • __str__ reconstruye la cadena que se pasó a ''analizar'' (o una cadena equivalente, es decir, una que generará una instancia de número de versión equivalente)
  • __repr__ genera código Python para recrear la instancia del número de versión
  • _cmp compara la instancia actual con otra instancia de la misma clase o una cadena (que se analizará en una instancia de la misma clase, por lo tanto, debe seguir las mismas reglas)

StrictVersion

Numeración de la versión para retentivos anales e idealistas de software. Implementa la interfaz estándar para las clases de número de versión como se describió anteriormente. Un número de versión consta de dos o tres componentes numéricos separados por puntos, con una etiqueta opcional de "prelanzamiento" en el extremo. La etiqueta previa a la publicación consiste en la letra ''a'' o ''b'' seguida de un número. Si los componentes numéricos de dos números de versión son iguales, entonces uno con una etiqueta de prelanzamiento siempre se considerará anterior (menor) que uno sin.

Los siguientes son números de versión válidos (se muestran en el orden que se obtendría al ordenar de acuerdo con la función cmp suministrada):

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

Los siguientes son ejemplos de números de versión no válidos:

1 2.7.2.2 1.3.a4 1.3pl1 1.3c4

El fundamento de este sistema de numeración de versiones se explicará en la documentación de distutils.

LooseVersion

Numeración de versiones para anarquistas y realistas de software. Implementa la interfaz estándar para las clases de número de versión como se describió anteriormente. Un número de versión consiste en una serie de números, separados por períodos o cadenas de letras. Al comparar números de versión, los componentes numéricos se compararán numéricamente, y los componentes alfabéticos léxicamente. Los siguientes son todos números de versión válidos, sin ningún orden en particular:

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

De hecho, no existe un número de versión no válida bajo este esquema; las reglas de comparación son simples y predecibles, pero es posible que no siempre proporcionen los resultados que desea (para alguna definición de "querer").


iré más por la opción de touple, haciendo una prueba, usando LooseVersion, obtengo en mi prueba el segundo más grande, (podría estar haciendo algo frente a mi ya que es la primera vez que uso esa biblioteca)

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)

y esto es lo que obtuve:

3.4.5 con Loose

(10, 5, 2) y con los touples


setuptools define parse_version() . Esto implementa PEP 0440 - Identificación de versión y también es capaz de analizar versiones que no siguen el PEP. Esta función es utilizada por easy_install y pip para manejar la comparación de versiones. De los docs :

Analizó una cadena de versión de proyecto como se define en PEP 440. El valor devuelto será un objeto que representa la versión. Estos objetos pueden ser comparados entre sí y clasificados. El algoritmo de clasificación es como lo define PEP 440 con la adición de que cualquier versión que no sea una versión válida de PEP 440 se considerará menos que cualquier versión válida de PEP 440 y las versiones no válidas continuarán clasificándose utilizando el algoritmo original.

El "algoritmo original" al que se hace referencia se definió en versiones anteriores de los documentos, antes de que existiera PEP 440.

Semánticamente, el formato es un cruce aproximado entre las clases StrictVersion y LooseVersion ; si le das versiones que funcionarían con StrictVersion , entonces se compararán de la misma manera. De lo contrario, las comparaciones son más como una forma "inteligente" de LooseVersion . Es posible crear esquemas de codificación de versiones patológicas que engañarán a este analizador, pero en la práctica deberían ser muy raros.

La documentation proporciona algunos ejemplos:

Si quiere estar seguro de que su esquema de numeración elegido funciona de la manera que cree que será, puede usar la función pkg_resources.parse_version() para comparar diferentes números de versión:

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


def versiontuple(v): return tuple(map(int, (v.split(".")))) >>> versiontuple("2.3.1") > versiontuple("10.1.1") False