¿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
yLooseVersion
; si le das versiones que funcionarían conStrictVersion
, entonces se compararán de la misma manera. De lo contrario, las comparaciones son más como una forma "inteligente" deLooseVersion
. 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