私は、それらの卵をsys.path
に追加するために、卵を含むディレクトリを歩いています。ディレクトリに同じ.Eggの2つのバージョンがある場合、最新のものだけを追加したいです。
ファイル名から名前とバージョンを抽出する正規表現r"^(?P<eggName>\w+)-(?P<eggVersion>[\d\.]+)-.+\.Egg$
があります。問題は、2.3.1
のような文字列であるバージョン番号の比較です。
文字列を比較しているので、10を超える2ソートですが、バージョンでは正しくありません。
>>> "2.3.1" > "10.1.1"
True
分割、解析、intへのキャストなどを行うことができ、最終的には回避策が得られます。しかし、これはPythonです Javaではない 。バージョン文字列を比較するエレガントな方法はありますか?
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
はサードパーティのユーティリティですが、 setuptools で使用されているため(おそらく既にインストール済みです)、現在の PEP 44 に準拠しています。バージョンが準拠している場合はpackaging.version.Version
を返し、準拠していない場合はpackaging.version.LegacyVersion
を返します。後者は常に有効なバージョンの前にソートされます。
まだ多くのソフトウェアで使用されている古代の代替手段は distutils.version
です。組み込みですが、文書化されておらず、置き換えられたものにのみ準拠しています 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'
ご覧のとおり、有効なPEP 440バージョンは「厳密ではない」と見なされているため、有効なバージョンとは何かという現代のPythonの概念とは一致しません。
distutils.version
はドキュメント化されていないため、 here は関連するdocstringです。
setuptoolsはparse_version()
を定義します。これは PEP 0440-バージョン識別 を実装し、PEPに準拠していないバージョンを解析することもできます。この関数は、バージョン比較を処理するためにeasy_install
および pip
によって使用されます。 docs から:
PEP 440で定義されているプロジェクトのバージョン文字列を解析しました。返される値は、バージョンを表すオブジェクトになります。これらのオブジェクトは互いに比較され、ソートされます。並べ替えアルゴリズムは、有効なPEP 440バージョンではないバージョンは有効なPEP 440バージョンよりも小さいと見なされ、無効なバージョンは元のアルゴリズムを使用して並べ替えを続けるという追加のPEP 440で定義されています。
参照される「元のアルゴリズム」は、PEP 440が存在する前に、古いバージョンのドキュメントで定義されていました。
意味的には、この形式はdistutilsの
StrictVersion
クラスとLooseVersion
クラスの大まかなクロスです。StrictVersion
で動作するバージョンを指定すると、同じ方法で比較されます。それ以外の場合は、比較はLooseVersion
の「よりスマートな」形式に似ています。このパーサーをだます病的バージョンコーディングスキームを作成することは可能ですが、実際には非常にまれです。
documentation はいくつかの例を提供します:
選択した番号付けスキームが思い通りに機能することを確認したい場合は、
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
Setuptoolsを使用していない場合、 packaging プロジェクトは、これと他のパッケージング関連機能を個別のライブラリに分割します。
from packaging import version
version.parse('1.0.3.dev')
from pkg_resources import parse_version
parse_version('1.0.3.dev')
def versiontuple(v):
return Tuple(map(int, (v.split("."))))
>>> versiontuple("2.3.1") > versiontuple("10.1.1")
False
バージョン文字列をタプルに変換し、そこから先に進むことの何が問題になっていますか?私には十分にエレガントだ
>>> (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のソリューションは、コードの見栄えの良い例です。
packaging パッケージが利用可能です。これにより、 PEP-44 ごとにバージョンを比較することができます。
>>> 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
semver パッケージを使用して、バージョンが semantic version 要件を満たしているかどうかを判断できます。これは、2つの実際のバージョンを比較することとは異なりますが、一種の比較です。
たとえば、バージョン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
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
setuptools
が行う方法では、pkg_resources.parse_version
関数を使用します。 PEP44に準拠する必要があります。
例:
#! /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
新しい依存関係を追加しないソリューションを探していました。次の(Python 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 __== '__main__':
VersionManager.test_compare_versions()
編集:タプル比較でバリアントを追加しました。もちろん、タプル比較のバリアントはより優れていますが、整数比較のバリアントを探していました