পাইথনের সংস্করণ সংখ্যাগুলি কীভাবে তুলনা করব?


235

আমি সেই ডিরেক্টরিতে হাঁটছি যেগুলিতে সেই ডিমগুলি যুক্ত করতে ডিম রয়েছে sys.path। ডিরেক্টরিতে যদি একই .egg এর দুটি সংস্করণ থাকে তবে আমি কেবল সর্বশেষতমটি যুক্ত করতে চাই।

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নথিভুক্ত হিসাবে , এখানে প্রাসঙ্গিক ডকাস্ট্রিংস।


2
দেখে মনে হচ্ছে নরমালাইজড ভার্সন আসবে না, যেমনটি এটি বাতিল করা হয়েছিল এবং লুজ ভার্সন এবং স্ট্রাইক ভার্শনটি তাই আর অবচিত হয় না।
তাইউই

12
এটি একটি কান্নার লজ্জা distutils.versionনিবন্ধকৃত।
জন ওয়াই

এটি অনুসন্ধান ইঞ্জিন ব্যবহার করে এবং সরাসরি version.pyউত্স কোডটি খুঁজে পেয়েছিল । খুব সুন্দর করে বললাম!
জোল

@ তারা তারা আরও ভাল, যেহেতু তারা পিইপি 440 অনুগত নয়।
উড়ন্ত ভেড়া

2
imho packaging.version.parseসংস্করণ তুলনা বিশ্বাস করা যায় না। parse('1.0.1-beta.1') > parse('1.0.0')উদাহরণস্বরূপ চেষ্টা করুন ।
ট্রোঁড

104

প্যাকেজিং লাইব্রেরির জন্য ইউটিলিটি রয়েছে সংস্করণের সাথে কাজ এবং অন্যান্য প্যাকেজিং-সম্পর্কিত কার্যকারিতা। এটি পিইপি 0440 প্রয়োগ করে - সংস্করণ সনাক্তকরণ এবং পিইপি অনুসরণ না করে এমন সংস্করণগুলি পার্স করতে সক্ষম। সংস্করণ পার্সিং এবং তুলনা প্রদানের জন্য এটি পাইপ এবং অন্যান্য সাধারণ পাইথন সরঞ্জাম ব্যবহার করে।

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

এটি আরও হালকা ও দ্রুত প্যাকেজ সরবরাহের জন্য সেটআপলগুলিতে এবং pkg_res উত্সগুলিতে মূল কোড থেকে বিভক্ত ছিল।


প্যাকেজিং লাইব্রেরির অস্তিত্বের আগে, এই কার্যকারিতাটি pkg_res উত্সে পাওয়া গিয়েছিল (এবং এখনও হতে পারে), সেটআপলগুলি সরবরাহ করে একটি প্যাকেজ। যাইহোক, এটি আর পছন্দ করা হয়নি কারণ সেটআপলগুলি আর ইনস্টল করার গ্যারান্টি নেই (অন্যান্য প্যাকেজিং সরঞ্জাম বিদ্যমান), এবং pkg_res્રોস আমদানি করার সময় প্রচুর সংস্থান ব্যবহার করে। তবে, সমস্ত দস্তাবেজ এবং আলোচনা এখনও প্রাসঙ্গিক।

parse_version()দস্তাবেজগুলি থেকে :

পিইপি 440 দ্বারা সংজ্ঞায়িত হিসাবে প্রকল্পের সংস্করণ স্ট্রিংটিকে পার্স করা হয়েছে returned প্রত্যাবর্তিত মানটি এমন একটি বস্তু হবে যা সংস্করণটি উপস্থাপন করে। এই বিষয়গুলি একে অপরের সাথে তুলনা করা এবং বাছাই করা যেতে পারে। বাছাই অ্যালগরিদম পিইপি 440 দ্বারা সংজ্ঞায়িত হিসাবে সংযোজন করা হয়েছে যে কোনও বৈধ পিইপি 440 সংস্করণ নয় এমন কোনও সংস্করণ কোনও বৈধ পিইপি 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"
kindall

6
এটি যেমন কিছু জন্য ব্যর্থ হবে versiontuple("1.0") > versiontuple("1")। সংস্করণগুলি একই, তবে তৈরি (1,)!=(1,0)
টিউপসগুলি

3
সংস্করণ 1 এবং সংস্করণ 1.0 কী অর্থে? সংস্করণ সংখ্যাগুলি ভাসমান নয়।
ক্যান্ডল করুন

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
আমি মনে করি যে এই উত্তরটি এমন একটি কোড সরবরাহের মাধ্যমে প্রসারিত করা যেতে পারে যা একটি পিইপি ৪৪০ স্ট্রিংকে একটি টুপলে রূপান্তর করে । আমি মনে করি আপনি এটি কোনও তুচ্ছ কাজ নয়। আমি মনে করি এটি যে প্যাকেজটির জন্য সেই অনুবাদটি সম্পাদন করে তা আরও ভাল ছেড়ে setuptoolsযায় pkg_resources

@ টাইলারগুবালা এটি এমন পরিস্থিতিতে একটি দুর্দান্ত উত্তর যেখানে আপনি জানেন যে সংস্করণটি সর্বদা "সরল" থাকবে। pkg_resources একটি বড় প্যাকেজ এবং এটি বিতরণযোগ্য এক্সিকিউটেবলের পরিবর্তে পুষ্পিত হতে পারে।
এরিক আরোনাস্টি

@ এরিক অ্যারোনস্টি আমি মনে করি বিতরণকারী এক্সিকিউটেবলের অভ্যন্তরের সংস্করণ নিয়ন্ত্রণ করা প্রশ্নটির পরিধি থেকে কিছুটা দূরে, তবে আমি সাধারণভাবে অন্তত সম্মত হই। আমি মনে করি যদিও এর পুনঃব্যবহারযোগ্যতা সম্পর্কে কিছু বলার আছে pkg_resourcesএবং সাধারণ প্যাকেজ নামকরণের অনুমানগুলি সর্বদা আদর্শ নাও হতে পারে।

এটি নিশ্চিত sys.version_info > (3, 6)বা যা কিছু করার জন্য দুর্দান্ত কাজ করে ।
Gqqnbig

7

নেই প্যাকেজিং যা আপনি অনুযায়ী সংস্করণ তুলনা অনুমতি দেবে প্যাকেজ পাওয়া যায়, PEP-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')>

পিইপি -440 সংস্করণের সাথে উত্তরাধিকার সংস্করণটির তুলনা করা।

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

3
packaging.version.Versionএবং এর মধ্যে পার্থক্যের বিষয়ে যারা ভাবছেন তাদের জন্য packaging.version.parse: "[ version.parse] একটি সংস্করণ স্ট্রিং নেয় এবং Versionসংস্করণটি বৈধ পিইপি 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

কিন্ডালের সমাধানের ভিত্তিতে আমার সম্পূর্ণ ফাংশন পোস্ট করা। আমি নেতৃস্থানীয় শূন্যগুলির সাথে প্রতিটি সংস্করণ বিভাগে প্যাডিং করে সংখ্যার সাথে মিশ্রিত কোনও বর্ণানুক্রমিক অক্ষরকে সমর্থন করতে সক্ষম হয়েছি।

যদিও তাঁর ওয়ান-লাইনারের ফাংশনের মতো সুন্দর নয়, এটি আলফা-সংখ্যাগত সংস্করণ সংখ্যাগুলির সাথে ভালভাবে কাজ করছে বলে মনে হচ্ছে। ( 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()

সম্পাদনা: টুপল তুলনার সাথে বৈকল্পিক যুক্ত হয়েছে। অবশ্যই টুপল তুলনা সহ ভেরিয়েন্টটি ভাল, তবে আমি পূর্ণসংখ্যার তুলনা সহ বৈকল্পিকটি খুঁজছিলাম


আমি কৌতূহলী যে কোন পরিস্থিতিতে এটি নির্ভরতা যুক্ত করা এড়ায়? পাইথন প্যাকেজ তৈরি করার জন্য আপনার কি প্যাকেজিং লাইব্রেরি (সেটআপলগুলি দ্বারা ব্যবহৃত) প্রয়োজন হবে না?
জোশিয়াহ এল।
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.