मैं पायथन में संस्करण संख्याओं की तुलना कैसे करूं?


236

मैं एक निर्देशिका चला रहा हूं जिसमें उन अंडों को जोड़ने के लिए अंडे शामिल हैं sys.path। यदि एक ही संस्करण के दो संस्करण हैं। निर्देशिका में, मैं केवल नवीनतम को जोड़ना चाहता हूं।

मेरे पास r"^(?P<eggName>\w+)-(?P<eggVersion>[\d\.]+)-.+\.egg$फ़ाइलनाम से नाम और संस्करण निकालने के लिए एक नियमित अभिव्यक्ति है। समस्या संस्करण संख्या की तुलना कर रही है, जो एक स्ट्रिंग की तरह है 2.3.1

चूंकि मैं स्ट्रिंग्स की तुलना कर रहा हूं, 10 से ऊपर 2 प्रकार, लेकिन यह संस्करणों के लिए सही नहीं है।

>>> "2.3.1" > "10.1.1"
True

मैं कुछ विभाजन, पार्सिंग, इंट टू कास्टिंग, आदि कर सकता था, और मुझे अंततः एक वर्कअराउंड मिलेगा। लेकिन यह पायथन है, जावा नहीं । वहाँ संस्करण तार की तुलना करने के लिए एक सुंदर तरीका है?

जवाबों:


367

का उपयोग करें packaging.version.parse

>>> 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एक तृतीय-पक्ष उपयोगिता है, लेकिन इसका उपयोग सेटपूल द्वारा किया जाता है (ताकि आप शायद पहले से ही इसे स्थापित कर चुके हैं) और वर्तमान पीईपी 440 के अनुरूप है ; packaging.version.Versionयदि संस्करण अनुरूप है और packaging.version.LegacyVersionयदि नहीं तो यह वापस आ जाएगा । उत्तरार्द्ध हमेशा मान्य संस्करणों से पहले सॉर्ट करेगा।

नोट : पैकेजिंग को हाल ही में सेटप्टूल में वर्गीकृत किया गया है


अभी भी बहुत सारे सॉफ्टवेयर द्वारा उपयोग किया जाने वाला एक प्राचीन विकल्प है distutils.version, लेकिन इसमें बनाया गया है जो केवल अलिखित पीईपी 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'

जैसा कि आप देख सकते हैं कि यह वैध पीईपी 440 संस्करणों को "सख्त नहीं" के रूप में देखता है और इसलिए आधुनिक पायथन की धारणा से मेल नहीं खाता है कि एक वैध संस्करण क्या है।

जैसा कि distutils.versionयह अनिर्दिष्ट है, यहाँ प्रासंगिक docstrings है।


2
ऐसा लगता है कि नॉर्मलाइज्ड वर्जन नहीं आ रहा है, क्योंकि यह अलग हो गया था, और LooseVersion और StrictVersion इसलिए अब वंचित नहीं हैं।
तैवे

12
यह रोना शर्म distutils.versionकी बात है।
Y पर जॉन वाई

इसे खोज इंजन का उपयोग करके पाया, और सीधे version.pyस्रोत कोड को खोजा । बहुत अच्छी तरह से डाला!
जोएल

@ बेहतर वे बेहतर कर रहे हैं, क्योंकि वे PEP 440 अनुरूप नहीं हैं।
भेड़

2
imho packaging.version.parseसंस्करणों की तुलना करने के लिए भरोसा नहीं किया जा सकता है। parse('1.0.1-beta.1') > parse('1.0.0')उदाहरण के लिए प्रयास करें ।
ट्रॉनड

104

पैकेजिंग पुस्तकालय के लिए उपयोगिताओं शामिल संस्करणों के साथ काम करने और अन्य पैकेजिंग से संबंधित कार्यक्षमता। यह PEP 0440 - संस्करण पहचान को लागू करता है और उन संस्करणों को पार्स करने में सक्षम है जो PEP का पालन नहीं करते हैं। यह संस्करण पार्सिंग और तुलना प्रदान करने के लिए पाइप, और अन्य सामान्य पायथन उपकरणों द्वारा उपयोग किया जाता है।

$ pip install packaging
from packaging.version import parse as parse_version
version = parse_version('1.0.3.dev')

इसे अधिक हल्के और तेज पैकेज प्रदान करने के लिए सेटपूल और pkg_resources में मूल कोड से अलग कर दिया गया था।


पैकेजिंग लाइब्रेरी के अस्तित्व में आने से पहले, यह कार्यशीलता (और अभी भी हो सकती है) pkg_resources में उपलब्ध है, जो सेटटॉपूल द्वारा प्रदान किया गया पैकेज है। हालाँकि, इसे अब पसंद नहीं किया जाता है क्योंकि सेटपूल अब स्थापित होने की गारंटी नहीं है (अन्य पैकेजिंग उपकरण मौजूद हैं), और आयातित होने पर pkg_resource विडंबना का उपयोग करता है। हालाँकि, सभी डॉक्स और चर्चा अभी भी प्रासंगिक हैं।

से parse_version()डॉक्स :

PEP 440 द्वारा परिभाषित के रूप में एक प्रोजेक्ट के संस्करण स्ट्रिंग को पार्स किया गया। लौटाया गया मान एक ऐसी वस्तु होगी जो संस्करण का प्रतिनिधित्व करती है। इन वस्तुओं की एक दूसरे से तुलना की जा सकती है। छँटाई एल्गोरिथ्म को PEP 440 द्वारा इस प्रकार परिभाषित किया गया है कि कोई भी संस्करण जो मान्य PEP 440 संस्करण नहीं है, उसे किसी भी मान्य PEP 440 संस्करण से कम माना जाएगा और अमान्य संस्करण मूल एल्गोरिथ्म का उपयोग करके छँटाई करते रहेंगे।

पीईपी 440 के अस्तित्व में आने से पहले संदर्भित "मूल एल्गोरिथ्म" डॉक्स के पुराने संस्करणों में परिभाषित किया गया था।

शब्दार्थ, प्रारूप डिस्टुटिल 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

57
def versiontuple(v):
    return tuple(map(int, (v.split("."))))

>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False

10
अन्य उत्तर मानक पुस्तकालय में हैं और पीईपी मानकों का पालन करते हैं।
क्रिस

1
उस मामले में आप map()फ़ंक्शन को पूरी तरह से हटा सकते हैं , क्योंकि परिणाम पहलेsplit() से ही तार है। लेकिन आप वैसे भी ऐसा नहीं करना चाहते हैं, क्योंकि उन्हें बदलने का पूरा कारण intयह है कि वे संख्या के रूप में ठीक से तुलना करें। नहीं तो "10" < "2"
किंडल

6
यह कुछ इस तरह से विफल हो जाएगा versiontuple("1.0") > versiontuple("1")। संस्करण समान हैं, लेकिन टुपल्स बनाए गए(1,)!=(1,0)
dawg

3
किस अर्थ में संस्करण 1 और संस्करण 1.0 समान हैं? वर्जन नंबर फ्लोट नहीं हैं।
kindall

12
नहीं, यह स्वीकृत उत्तर नहीं होना चाहिए । शुक्र है, यह नहीं है। वर्जन स्पेसर्स का विश्वसनीय पार्सिंग सामान्य मामले में गैर-तुच्छ (यदि व्यावहारिक रूप से व्यावहारिक नहीं है) है। पहिया को सुदृढ़ न करें और फिर इसे तोड़ने के लिए आगे बढ़ें। जैसा कि परमानंद ऊपर सुझाव देता है , बस उपयोग करें distutils.version.LooseVersion। इसके लिए वहीं है।
सेसिल करी

12

संस्करण स्ट्रिंग को टुपल में बदलने और वहां से जाने में क्या गलत है? मेरे लिए बहुत सुंदर लगता है

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

@ किंडल का समाधान एक त्वरित उदाहरण है कि कोड कितना अच्छा लगेगा।


1
मुझे लगता है कि इस उत्तर का विस्तार कोड प्रदान करके किया जा सकता है जो एक PEP440 स्ट्रिंग के एक ट्यूपल में परिवर्तन करता है । मुझे लगता है कि आप पाएंगे कि यह कोई मामूली काम नहीं है। मुझे लगता है कि यह उस पैकेज के लिए बेहतर है जो उस अनुवाद को करता है setuptools, जो है pkg_resources

@TylerGubala यह उन स्थितियों में एक महान जवाब है जहां आप जानते हैं कि संस्करण है और हमेशा "सरल" होगा। pkg_resource एक बड़ा पैकेज है और यह वितरित किए गए निष्पादन योग्य को बल्कि फूला हुआ होने का कारण बन सकता है।
एरिक एरोनेस्टी

@ एरिक एरोनिटी मुझे लगता है कि वितरित निष्पादकों के अंदर संस्करण नियंत्रण कुछ हद तक प्रश्न के दायरे से बाहर है, लेकिन मैं सहमत हूं, आम तौर पर कम से कम। हालांकि मुझे लगता है कि पुन: प्रयोज्य के बारे में कुछ कहा जाना है pkg_resources, और सरल पैकेज के नामकरण की धारणाएं हमेशा आदर्श नहीं हो सकती हैं।

यह सुनिश्चित करने के लिए महान काम करता है sys.version_info > (3, 6)या जो कुछ भी।
Gqqnbig

7

है पैकेजिंग जो आप के अनुसार संस्करणों की तुलना करने की अनुमति देगा पैकेज उपलब्ध, पीईपी-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

3
के बीच अंतर के बारे में सोच रहे लोगों के लिए packaging.version.Versionऔर packaging.version.parse: "[ version.parse] एक संस्करण स्ट्रिंग लेता है और इसे पार्स करेगा जैसे Versionकि संस्करण एक मान्य PEP 440 संस्करण है, अन्यथा यह इसे एक के रूप में पार्स करेगा LegacyVersion।" (जबकि ; स्रोतversion.Version उठाएगा )InvalidVersion
ब्रहम स्नाइडर

5

यदि कोई संस्करण किसी अर्थ संस्करण को संतुष्ट करता है, तो आप यह निर्धारित करने के लिए सेवर पैकेज का उपयोग कर सकते हैं आवश्यकता को पूरा । यह दो वास्तविक संस्करणों की तुलना करने के समान नहीं है, लेकिन तुलना का एक प्रकार है।

उदाहरण के लिए, संस्करण 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

3

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

2

जिस तरह से यह setuptoolsकरता है, यह pkg_resources.parse_versionफ़ंक्शन का उपयोग करता है । यह PEP440 होना चाहिए अनुरूप ।

उदाहरण:

#! /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

pkg_resourcesका हिस्सा है setuptools, जो पर निर्भर करता है packaging। अन्य उत्तरों को देखें जो चर्चा करते हैं packaging.version.parse, जिनके समान कार्यान्वयन है pkg_resources.parse_version
जेड

0

मैं एक ऐसे समाधान की तलाश में था जो किसी नई निर्भरता को न जोड़े। निम्नलिखित (पायथन 3) समाधान देखें:

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

संपादित करें: टपल तुलना के साथ जोड़ा गया संस्करण। बेशक टपल तुलना के साथ वेरिएंट अच्छे हैं, लेकिन मैं पूर्णांक तुलना के साथ वेरिएंट की तलाश कर रहा था


मैं इस बात से उत्सुक हूं कि यह किस स्थिति में निर्भरता जोड़ने से बचता है? अजगर पैकेज बनाने के लिए क्या आपको पैकेजिंग लाइब्रेरी (सेटटॉप्स द्वारा उपयोग किए जाने वाले) की आवश्यकता नहीं होगी?
जोशियाह एल।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.