__cmp__の代わりに__lt__


100

Python 2.xには、比較演算子、__cmp__またはなどの「豊富な比較演算子」をオーバーロードする方法が2つあります__lt__豊富な比較オーバーロードが好ましいと言われていますが、なぜそうなのでしょうか。

豊富な比較演算子はそれぞれを実装する方が簡単ですが、それらのいくつかをほぼ同じロジックで実装する必要があります。ただし、組み込みcmpおよびタプルの順序付けを使用できる場合は、__cmp__非常に単純になり、すべての比較が実行されます。

class A(object):
  def __init__(self, name, age, other):
    self.name = name
    self.age = age
    self.other = other
  def __cmp__(self, other):
    assert isinstance(other, A) # assumption for this example
    return cmp((self.name, self.age, self.other),
               (other.name, other.age, other.other))

この単純さは、豊富な比較の6(!)すべてをオーバーロードするよりもはるかによく私のニーズを満たしているようです。(ただし、「スワップされた引数」/反映された動作に依存している場合は、「わずか」4に下げることができますが、私の考えでは、複雑さの正味の増加になります。)

オーバーロードするだけの場合に知っておく必要のある予期しない落とし穴はあり__cmp__ますか?

私は理解して<<===、などの事業者が他の目的のために、オーバーロードすることができ、そして、彼らが好きな任意のオブジェクトを返すことができます。私はそのアプローチのメリットについて尋ねているのではなく、数値に対して意味するのと同じ意味でこれらの演算子を比較に使用する場合の違いについてのみ尋ねています。

更新: Christopherが指摘したようにcmpは3.xで姿を消しています。上記と同じくらい簡単に比較を実装できる代替手段はあります__cmp__か?


5
あなたの最後の質問に対する私の答えを見てください、しかし実際にはあなたを含む多くのクラスのために物事をさらに簡単にするデザインがあります(今あなたはそれを適用するためにミックスイン、メタクラス、またはクラスデコレーターが必要です):重要な特別なメソッドが存在する場合、値のタプルを返す必要があり、すべてのコンパレータとハッシュはそのタプルに関して定義されます。Guidoは彼に説明したときに私のアイデアを気に入っていましたが、それから私は他のことで忙しくなり、PEPを書こうとはしませんでした...多分3.2 ;-)。その間、私はそのために自分のミックスインを使い続けます!-)
Alex Martelli、

回答:


90

__lt__はい、たとえば、ミックスインクラス(またはメタクラス、または好みに合わせてクラスデコレータ)を使用して、すべてを実装するのは簡単です。

例えば:

class ComparableMixin:
  def __eq__(self, other):
    return not self<other and not other<self
  def __ne__(self, other):
    return self<other or other<self
  def __gt__(self, other):
    return other<self
  def __ge__(self, other):
    return not self<other
  def __le__(self, other):
    return not other<self

これで、クラスは__lt__、ComparableMixinから正確に定義して複数継承できます(他に必要なベースがある場合は、その後に)。クラスデコレータは非常によく似ており、デコレートする新しいクラスの属性として同様の関数を挿入するだけです(結果として、実行時に、メモリの観点から見ても同じくらいのコストで、微視的に高速になる可能性があります)。

もちろん、クラスに(eg)__eq__とを実装する特に高速な方法がある__ne__場合は、それらを直接定義して、ミックスインのバージョンが使用されないようにする必要があります(たとえば、の場合dict)-実際__ne__に定義して、それとして:

def __ne__(self, other):
  return not self == other

しかし、上記のコードでは、<;-) のみを使用するという対称性を維持したいと考えました。理由として__cmp__、我々がいるので行かなければならなかった、やってい__lt__や友人、なぜ、別の周りにまったく同じことを行うための別の方法を保ちますか?すべてのPythonランタイム(Classic、Jython、IronPython、PyPyなど)で非常に重要です。コードは間違いなくそこから(Cはの「スピリットCの」セクションで同じ原理を持っているタスクを実行するための理想的な1つの明白な方法があるはずべきことPythonの原則-バグを持っていないでしょうがありませんコードですISO標準、btw)。

これは、私たちが物事を禁止する我々の方法の外出を意味するものではありません(例えば、ミックスインといくつかの用途のためのクラスのデコレータの間にほぼ等価)が、それは間違いありません、我々はコンパイラとのコードを持ち歩くのは好きではないということを意味/または、まったく同じタスクを実行する複数の同等のアプローチをサポートするためだけに冗長に存在するランタイム。

さらに編集:実際__key__に、質問へのコメントで述べたように、質問のメソッドを含む多くのクラスに比較とハッシュを提供するためのより良い方法があります- メソッド。私はそのためのPEPを書くことに慣れなかったので、もしそれが好きなら、あなたは現在それをMixin(&c)で実装しなければなりません:

class KeyedMixin:
  def __lt__(self, other):
    return self.__key__() < other.__key__()
  # and so on for other comparators, as above, plus:
  def __hash__(self):
    return hash(self.__key__())

他のインスタンスとのインスタンスの比較が煮詰めて、いくつかのフィールドを持つ各タプルを比較することは非常に一般的なケースです-そして、ハッシュはまったく同じ基準で実装されるべきです。__key__直接必要とする特別な方法アドレス。


@Rさん、遅れて申し訳ありません。パテ、とにかく編集しなければならなかったので、急ぐよりもできる限り詳細な答えを提供する必要があると判断しました(そして、私はPEPpingに回ったことのない古い重要なアイデアとその方法を提案するためにもう一度編集しました)ミックスインで実装する)。
Alex Martelli、

私はその重要なアイデアが本当に好きです。それを使用して、それがどのように感じられるかを見ていきます。(予約済みの名前ではなく、cmp_keyまたは_cmp_keyという名前が付けられています。)

TypeError: Cannot create a consistent method resolution order (MRO) for bases object, ComparableMixinPython 3でこれを試してみると、gist.github.com / 2696496
Adam Parkin

2
Python 2.7 + / 3.2 + functools.total_orderingでは、独自のをビルドする代わりに使用できますComparableMiximjmagnussonの回答で

4
を使用<しているため__eq__、Python 3での実装に使用するのはかなり悪い考えですTypeError: unorderable types
Antti Haapala

49

このケースを簡略化するために、Python 2.7 + / 3.2 +にはクラスデコレータfunctools.total_orderingがあり、これを使用してAlexの提案を実装できます。ドキュメントの例:

@total_ordering
class Student:
    def __eq__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) ==
                (other.lastname.lower(), other.firstname.lower()))
    def __lt__(self, other):
        return ((self.lastname.lower(), self.firstname.lower()) <
                (other.lastname.lower(), other.firstname.lower()))

9
total_ordering__ne__ただし、実装されていないので注意してください!
Flimm 2013

3
@Flimm、それはしませんが__ne__。しかし、__ne__それはに委譲するデフォルトの実装があるためです__eq__。したがって、ここで注意することは何もありません。
Jan Hudec

少なくとも1つの順序付け操作を定義する必要があります:<> <=> = ....!の場合、eqは合計順序として必要ありません。a <b and b <a then a = b
Xanlantos

9

これはPEP 207-豊富な比較でカバーされています

また、__cmp__Python 3.0では廃止されます。(それが上に存在しないことに注意してくださいhttp://docs.python.org/3.0/reference/datamodel.htmlそれがオンになっているhttp://docs.python.org/2.7/reference/datamodel.html


PEPは、NumPyユーザーがA <Bがシーケンスを返すことを望んでいる方法で、豊富な比較が必要な理由のみに関係しています。

私はそれが確実になくなることを知りませんでした、これは私を悲しくさせます。(しかし、それを指摘してくれてありがとう。)

PEPは、「なぜ」それらが好ましいのかについても説明します。本質的には効率に帰着します。1.オブジェクトに意味のない操作(順序付けされていないコレクションなど)を実装する必要はありません。2.一部のコレクションは、特定の種類の比較に対して非常に効率的な操作を行います。豊富な比較により、インタープリターはそれを定義すればそれを利用できます。
クリストファー

1
再1、それらが意味をなさない場合は、cmpを実装しないでください。Re 2には両方のオプションがあるため、必要に応じて最適化でき、プロトタイピングとテストも迅速に行えます。どちらが削除されたのかはどちらにもわかりません。(本質的に、私にとっては開発者の効率に帰着します。)cmpフォールバックが設定されていると、豊富な比較は効率が低下する可能性がありますか?それは私には意味がありません。

1
@R。パテ、私が私の回答で説明しようとするように、一般性には実質的な損失はありません(ミックスイン、デコレータ、またはメタクラスでは、必要に応じて<だけですべてを簡単に定義できるため)、したがって、すべてのPython実装で実行できます永久にcmpにフォールバックする冗長なコード(Pythonユーザーが2つの同等の方法で表現できるようにするため)は、Pythonの粒度に対して100%実行されます。
Alex Martelli、

2

(コメントを考慮に入れるために6/17/17を編集。)

上記の比較可能なミックスインの答えを試しました。「なし」で困りました。以下は、「なし」との等価比較を処理する変更バージョンです。(Noneとの不等比較を意味論に欠けるものとして悩む理由はありませんでした):


class ComparableMixin(object):

    def __eq__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other and not other<self

    def __ne__(self, other):
        return not __eq__(self, other)

    def __gt__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return other<self

    def __ge__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not self<other

    def __le__(self, other):
        if not isinstance(other, type(self)): 
            return NotImplemented
        else:
            return not other<self    

それselfがあなたのシングルトンNoneであるNoneTypeと同時にどのように実装できると思いますComparableMixinか?そして実際、このレシピは、Python 3に悪いです
アンティHaapala

3
selfになることないのでNone、そのブランチは完全に移動できます。使用しないでくださいtype(other) == type(None)。単に使用しますother is None。特別なケースNoneではなく、他の型がの型のインスタンスであるかどうかをテストし、そうでない場合selfNotImplementedシングルトンを返しますif not isinstance(other, type(self)): return NotImplemented。すべてのメソッドでこれを行います。Pythonは、代わりに他のオペランドに答えを提供する機会を与えることができます。
Martijn Pieters

1

Alex MartelliのComparableMixinKeyedMixin回答に触発されて、次のミックスインを思いつきました。これにより_compare_to()、と同様のキーベースの比較を使用する単一のメソッドを実装KeyedMixinできますが、クラスはのタイプに基づいて最も効率的な比較キーを選択できますother。(このミックスインは、順序性ではなく同等性をテストできるオブジェクトにはあまり役立ちません)。

class ComparableMixin(object):
    """mixin which implements rich comparison operators in terms of a single _compare_to() helper"""

    def _compare_to(self, other):
        """return keys to compare self to other.

        if self and other are comparable, this function 
        should return ``(self key, other key)``.
        if they aren't, it should return ``None`` instead.
        """
        raise NotImplementedError("_compare_to() must be implemented by subclass")

    def __eq__(self, other):
        keys = self._compare_to(other)
        return keys[0] == keys[1] if keys else NotImplemented

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        keys = self._compare_to(other)
        return keys[0] < keys[1] if keys else NotImplemented

    def __le__(self, other):
        keys = self._compare_to(other)
        return keys[0] <= keys[1] if keys else NotImplemented

    def __gt__(self, other):
        keys = self._compare_to(other)
        return keys[0] > keys[1] if keys else NotImplemented

    def __ge__(self, other):
        keys = self._compare_to(other)
        return keys[0] >= keys[1] if keys else NotImplemented
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.