Estoy caminando un directorio que contiene los huevos para agregar los huevos a la sys.path. Si hay dos versiones de la misma .huevo en el directorio, quiero añadir sólo el más reciente.

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

Ya que estoy comparando cadenas, 2 clases por encima de 10, pero eso no es cierto para las versiones.

>>> "2.3.1" > "10.1.1"
True

Yo podría hacer algo de dividir, de análisis, de fundición int, etc., y me gustaría obtener finalmente una solución. Pero esto es Python, no Java. Es allí una manera elegante para comparar cadenas de versión?

InformationsquelleAutor BorrajaX | 2012-08-09

9 Comentarios

  1. 310

    Uso embalaje.versión.analizar.

    >>> 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
    >>> isinstance(version.parse("1.3.a4"), version.Version)
    True
    >>> isinstance(version.parse("1.3.xy123"), version.LegacyVersion)
    True
    >>> version.Version("1.3.xy123")
    Traceback (most recent call last):
    ...
    packaging.version.InvalidVersion: Invalid version: '1.3.xy123'

    packaging.version.parse es una utilidad de terceros, sino que es utilizada por setuptools (por lo que probablemente usted ya lo tiene instalado) y se adecúa a la actual PEP 440; devolverá un packaging.version.Version si la versión es compatible y un packaging.version.LegacyVersion si no. El último siempre ordenar antes de versiones válidas.


    Un antiguo alternativa que todavía es utilizado por una gran cantidad de software es distutils.la versión, construido en pero indocumentados y válidas sólo para el sustituida PEP 386;

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

    Como se puede ver se ve válido PEP 440 versiones como «no estricto» y por lo tanto no coincide con la moderna Python noción de lo que es una versión válida es.

    Como distutils.version es indocumentados, aquí‘s la correspondiente docstrings.

    • Parece NormalizedVersion no vendrá, como fue reemplazado, y LooseVersion y StrictVersion por lo tanto no son obsoletas.
    • Es una verdadera lástima distutils.version es indocumentados.
    • se encontró que el uso de motor de búsqueda, y la búsqueda directamente el version.py código fuente. Muy bien puestos!
    • Para algunos definición de. Buen punto. +1
    • ellos son una mejor opción, ya que no son PEP 440 compatible.
    • Cuenta que la aplicación de LooseVersion puede conducir a muy raras excepciones si se pasan al constructor de cualquier cosa que se evalúa a Falso. En particular, LooseVersion(Ninguno) y LooseVersion(«) son aceptadas en silencio en el momento de construcción, sólo para aumentar oscuro excepciones cuando se quiere comparar a otro, ya que el init método hizo exactamente nada.
    • Puedo obtener AttributeError: module 'packaging' has no attribute 'version' cuando trato de usar el primer método que usted menciona. Usando Python 3.7.3
    • en mi humilde opinión packaging.version.parse no puede ser de confianza para comparar versiones. Trate de parse('1.0.1-beta.1') > parse('1.0.0') por ejemplo.
    • Puedo obtener True, que es lo que me esperaba. ¿Qué haría usted, y qué esperabas?
    • He añadido una respuesta que creo que resuelve el problema.
    • Gracias! Sí, mi prueba fue contra semver y no pep440 que es por eso que tengo wonky resultados.

  2. 96

    setuptools define parse_version(). Esto implementa PEP 0440 — Identificación de la Versión de y también es capaz de analizar las versiones que no siga el PEP. Esta función se utiliza por easy_install y pip para manejar la comparación de versiones. Desde el docs:

    Analiza un proyecto de la cadena de versión como se define por PEP 440. El valor devuelto será un objeto que representa la versión. Estos objetos pueden ser comparados entre sí y ordenados. El algoritmo de ordenación es como se define por PEP 440 con la adición de que cualquier versión que no es válido PEP 440 versión será considerada menos que cualquier válido PEP 440 versión y la versión no válida versiones continuará clasificación mediante el algoritmo original.

    El «algoritmo original» se hace referencia se define en las versiones anteriores de los documentos, antes de que PEP 440 existido.

    Semánticamente, el formato es un áspero cruce entre distutils’ StrictVersion y LooseVersion clases; si le das versiones que funcionan con StrictVersion, entonces van a comparar de la misma manera. De lo contrario, las comparaciones son más como un «más inteligente» formulario de LooseVersion. Es posible crear patológico versión esquemas de codificación que va a engañar a este parser, pero deben ser muy raros en la práctica.

    La documentación proporciona algunos ejemplos:

    Si usted quiere estar seguro de que su elegido un esquema de numeración de las obras de la
    forma de pensar, puede utilizar el pkg_resources.parse_version()
    la función de 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

    Si no estás usando setuptools, el embalaje proyecto se divide este y otros envases relacionados con la funcionalidad en una biblioteca independiente.

    from packaging import version
    version.parse('1.0.3.dev')
    
    from pkg_resources import parse_version
    parse_version('1.0.3.dev')
  3. 54
    def versiontuple(v):
        return tuple(map(int, (v.split("."))))
    
    >>> versiontuple("2.3.1") > versiontuple("10.1.1")
    False
    • Las otras respuestas son en la biblioteca estándar y siga PEP normas.
    • Gracias por esto! Por cambiar el ‘int’ tipo en la función de mapa para ‘str’ esto también puede fácilmente acomodar tanto numéricos y alfanuméricos de los números de versión. def versiontuple(v): return tuple(map(str, (v.split("."))))
    • En ese caso se puede quitar el map() función enteramente, como el resultado de split() es ya cadenas. Pero usted no quiere hacer eso de todos modos, porque toda la razón para cambiar a int es así que comparar adecuadamente como números. De lo contrario,"10" < "2".
    • Buen punto. Yo era capaz de evitar esto mediante el relleno de cada elemento de la tupla con ceros a la izquierda utilizando el zfill() función.
    • Esto se producirá por algo como versiontuple("1.0") > versiontuple("1"). Las versiones son las mismas, pero las tuplas creado (1,)!=(1,0)
    • ¿En qué sentido son de la versión 1 y la versión 1.0 de la misma? Los números de versión no flota.
    • No, esto debe no es el aceptado la respuesta. Afortunadamente, no lo es. Fiable el análisis de la versión especificadores no es trivial (por no decir prácticamente imposible) en el caso general. No reinventar la rueda y, a continuación, proceder a romper. Como ecatmur sugiere anterior, sólo tiene que utilizar distutils.version.LooseVersion. Eso es lo que está allí.
    • Creo que una actualización está en orden a esto, y quisiera señalar que setuptools, que es un poco el «de facto» solución de gestión de paquetes en Python que utiliza actualmente pkg_resources.parse_version para realizar esta tarea.
    • cuando el empaquetado de la aplicación de las otras respuestas requieren que usted agregue a todos los de distutils o todos de los envases y pkg_resources … que son un poco de hinchazón. esta es una respuesta útil que funciona la mayor parte del tiempo – y no conduce al paquete de la hinchazón. realmente depende del contexto.

  4. 11

    Lo que está mal con la transformación de la cadena de versión en una tupla e ir de 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

    @kindall la solución es un ejemplo rápido de cómo el código sería.

    • Creo que esta respuesta podría ser ampliado por prestación de código que realiza la transformación de un PEP440 cadena en una tupla. Creo que usted encontrará que no es una tarea trivial. Creo que es mejor dejar que el paquete que realiza la traducción de setuptools, que es pkg_resources.
    • esta es una gran respuesta en situaciones donde se sabe que la versión es y siempre será «sencillo». pkg_resources es un gran paquete y puede causar un ejecutable distribuido a ser bastante hinchado.
    • Aronesty creo que la versión de control dentro de la distribución de los ejecutables es algo fuera del alcance de la pregunta, pero estoy de acuerdo, en general, al menos. Sin embargo, creo que hay algo que decir acerca de la reutilización de pkg_resources, y que los supuestos de simple paquete de nomenclatura no siempre puede ser ideal.
  5. 7

    Hay embalaje paquete disponible, lo que le permitirá comparar versiones como por PEP-440, así como las versiones anteriores.

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

    Legado de compatibilidad de versión:

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

    Comparar legado versión con PEP-440 versión.

    >>> LegacyVersion('1.2.8.5-5-gdeadbeef') < Version('1.2.8.6')
    True
    • Para los que preguntan acerca de la diferencia entre packaging.version.Version y packaging.version.parse: «[version.parse] toma una cadena de versión y analizarlo como un Version si la versión es válida PEP 440 versión, de lo contrario, se analizará como un LegacyVersion.» (mientras que version.Version aumentaría InvalidVersion; fuente)
  6. 5

    Puede utilizar el semver paquete para determinar si una versión satisface una semántica de la versión requisito. Esta no es la misma como la comparación de dos versiones, pero es un tipo de comparación.

    Por ejemplo, la versión 3.6.0+1234 debe ser el mismo que 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
  7. 2

    La publicación de mi completa de la función que se basa en Kindall de la solución. Yo era capaz de soportar cualquier carácter alfanumérico mezclados con los números de relleno de cada versión de la sección con ceros a la izquierda.

    Aunque ciertamente no es tan bonita como su one-liner función, parece que funciona bien con alfa-numérico de los números de versión. (Sólo asegúrese de establecer la zfill(#) valor apropiadamente si usted tiene el tiempo las cadenas en el 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
  8. 2

    La forma en que setuptools lo hace, utiliza el pkg_resources.parse_version función. Debe ser PEP440 compatible.

    Ejemplo:

    #! /usr/bin/python
    # -*- coding: utf-8 -*-
    """Example comparing two PEP440 formatted versions
    """
    import pkg_resources
    
    VERSION_A = pkg_resources.parse_version("1.0.1-beta.1")
    VERSION_B = pkg_resources.parse_version("v2.67-rc")
    VERSION_C = pkg_resources.parse_version("2.67rc")
    VERSION_D = pkg_resources.parse_version("2.67rc1")
    VERSION_E = pkg_resources.parse_version("1.0.0")
    
    print(VERSION_A)
    print(VERSION_B)
    print(VERSION_C)
    print(VERSION_D)
    
    print(VERSION_A==VERSION_B) #FALSE
    print(VERSION_B==VERSION_C) #TRUE
    print(VERSION_C==VERSION_D) #FALSE
    print(VERSION_A==VERSION_E) #FALSE
  9. 0

    Yo estaba buscando una solución que no agregan nuevas dependencias. Echa un vistazo a los siguientes (Python 3) solución:

    class VersionManager:
    @staticmethod
    def compare_version_tuples(
    major_a, minor_a, bugfix_a,
    major_b, minor_b, bugfix_b,
    ):
    """
    Compare two versions a and b, each consisting of 3 integers
    (compare these as tuples)
    version_a: major_a, minor_a, bugfix_a
    version_b: major_b, minor_b, bugfix_b
    :param major_a: first part of a
    :param minor_a: second part of a
    :param bugfix_a: third part of a
    :param major_b: first part of b
    :param minor_b: second part of b
    :param bugfix_b: third part of b
    :return:    1 if a  > b
    0 if a == b
    -1 if a  < b
    """
    tuple_a = major_a, minor_a, bugfix_a
    tuple_b = major_b, minor_b, bugfix_b
    if tuple_a > tuple_b:
    return 1
    if tuple_b > tuple_a:
    return -1
    return 0
    @staticmethod
    def compare_version_integers(
    major_a, minor_a, bugfix_a,
    major_b, minor_b, bugfix_b,
    ):
    """
    Compare two versions a and b, each consisting of 3 integers
    (compare these as integers)
    version_a: major_a, minor_a, bugfix_a
    version_b: major_b, minor_b, bugfix_b
    :param major_a: first part of a
    :param minor_a: second part of a
    :param bugfix_a: third part of a
    :param major_b: first part of b
    :param minor_b: second part of b
    :param bugfix_b: third part of b
    :return:    1 if a  > b
    0 if a == b
    -1 if a  < b
    """
    # --
    if major_a > major_b:
    return 1
    if major_b > major_a:
    return -1
    # --
    if minor_a > minor_b:
    return 1
    if minor_b > minor_a:
    return -1
    # --
    if bugfix_a > bugfix_b:
    return 1
    if bugfix_b > bugfix_a:
    return -1
    # --
    return 0
    @staticmethod
    def test_compare_versions():
    functions = [
    (VersionManager.compare_version_tuples, "VersionManager.compare_version_tuples"),
    (VersionManager.compare_version_integers, "VersionManager.compare_version_integers"),
    ]
    data = [
    # expected result, version a, version b
    (1, 1, 0, 0, 0, 0, 1),
    (1, 1, 5, 5, 0, 5, 5),
    (1, 1, 0, 5, 0, 0, 5),
    (1, 0, 2, 0, 0, 1, 1),
    (1, 2, 0, 0, 1, 1, 0),
    (0, 0, 0, 0, 0, 0, 0),
    (0, -1, -1, -1, -1, -1, -1),  # works even with negative version numbers :)
    (0, 2, 2, 2, 2, 2, 2),
    (-1, 5, 5, 0, 6, 5, 0),
    (-1, 5, 5, 0, 5, 9, 0),
    (-1, 5, 5, 5, 5, 5, 6),
    (-1, 2, 5, 7, 2, 5, 8),
    ]
    count = len(data)
    index = 1
    for expected_result, major_a, minor_a, bugfix_a, major_b, minor_b, bugfix_b in data:
    for function_callback, function_name in functions:
    actual_result = function_callback(
    major_a=major_a, minor_a=minor_a, bugfix_a=bugfix_a,
    major_b=major_b, minor_b=minor_b, bugfix_b=bugfix_b,
    )
    outcome = expected_result == actual_result
    message = "{}/{}: {}: {}: a={}.{}.{} b={}.{}.{} expected={} actual={}".format(
    index, count,
    "ok" if outcome is True else "fail",
    function_name,
    major_a, minor_a, bugfix_a,
    major_b, minor_b, bugfix_b,
    expected_result, actual_result
    )
    print(message)
    assert outcome is True
    index += 1
    # test passed!
    if __name__ == '__main__':
    VersionManager.test_compare_versions()

    EDIT: añadido variante con tupla de comparación. Por supuesto, la variante con tupla comparación es más bonito, pero yo estaba buscando la variante con la comparación de enteros

Dejar respuesta

Please enter your comment!
Please enter your name here