Python 3でクラスを比較可能にする標準的な方法は何ですか? (たとえば、ID で。)
5 に答える
__lt__
クラスを比較可能にするには、クラスを で実装して装飾するだけですfunctools.total_ordering
。__eq__
可能であれば、メソッドも提供する必要があります。これにより残りの比較演算子が提供されるため、それらを自分で記述する必要はありません。
比較関数の完全なセットとして、次の mixin を使用しました。たとえば、モジュールに mixin.py を入れることができます。
class ComparableMixin(object):
def _compare(self, other, method):
try:
return method(self._cmpkey(), other._cmpkey())
except (AttributeError, TypeError):
# _cmpkey not implemented, or return different type,
# so I can't compare with "other".
return NotImplemented
def __lt__(self, other):
return self._compare(other, lambda s, o: s < o)
def __le__(self, other):
return self._compare(other, lambda s, o: s <= o)
def __eq__(self, other):
return self._compare(other, lambda s, o: s == o)
def __ge__(self, other):
return self._compare(other, lambda s, o: s >= o)
def __gt__(self, other):
return self._compare(other, lambda s, o: s > o)
def __ne__(self, other):
return self._compare(other, lambda s, o: s != o)
上記の mixin を使用するには、ソート時に使用される key() 関数と同様に、比較可能なオブジェクトのキーを返す _cmpkey() メソッドを実装する必要があります。実装は次のようになります。
>>> from .mixin import ComparableMixin
>>> class Orderable(ComparableMixin):
...
... def __init__(self, firstname, lastname):
... self.first = firstname
... self.last = lastname
...
... def _cmpkey(self):
... return (self.last, self.first)
...
... def __repr__(self):
... return "%s %s" % (self.first, self.last)
...
>>> sorted([Orderable('Donald', 'Duck'),
... Orderable('Paul', 'Anka')])
[Paul Anka, Donald Duck]
total_ordering レシピの代わりにこれを使用する理由は、このバグです。これは Python 3.4 で修正されましたが、多くの場合、古いバージョンの Python もサポートする必要があります。
私はそれを行うための本当にハックな方法を考えました。これは、あなたが最初にやろうとしていたことと同じ精神です。クラスオブジェクトに関数を追加する必要はありません。どのクラスでも機能します。
max(((f(obj), obj) for obj in obj_list), key=lambda x: x[0])[1]
私は本当にそれが好きではないので、同じことをするより簡潔なものがあります:
def make_pair(f, obj):
return (f(obj), obj)
def gen_pairs(f, obj_list):
return (make_pair(f, obj) for obj in obj_list)
def item0(tup):
return tup[0]
def max_obj(f, obj_list):
pair = max(gen_pairs(f, obj_list), key=item0)
return pair[1]
obj_list
または、が常にリストのようなインデックス可能なオブジェクトである場合は、このワンライナーを使用できます。
obj_list[max((f(obj), i) for i, obj in enumerate(obj_list))[1]]
f(obj)
これには、同じ値を返すようなオブジェクトが複数ある場合に、どのオブジェクトを取得するかがわかるという利点があります。つまり、インデックスが最も高いオブジェクト、つまりリスト内の最新のオブジェクトです。リストの最初のものが必要な場合は、キー関数を使用してそれを行うことができます。
あなたはこれをやろうとしていると言った:
max((f(obj), obj) for obj in obj_list)[1]
これを行うだけです:
max(f(obj) for obj in obj_list)
編集:または、ニブラーが言ったように: max(obj_list, key=f)
しかし、あなたは最大オブジェクトへのオブジェクト参照が必要だと gnibbler に伝えました。これが最も簡単だと思います:
def max_obj(obj_list, max_fn):
if not obj_list:
return None
obj_max = obj_list[0]
f_max = max_fn(obj)
for obj in obj_list[1:]:
if max_fn(obj) > f_max:
obj_max = obj
return obj_max
obj = max_obj(obj_list)
もちろん、空のリストの max_obj() を見つけようとする場合は、何も返さないのではなく、例外を発生させたい場合があります。