web-dev-qa-db-ja.com

__cmp__の代わりに__lt__

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

リッチ比較演算子はそれぞれを実装するのが簡単ですが、それらのいくつかをほぼ同一のロジックで実装する必要があります。ただし、組み込みのcmpおよびTupleの順序付けを使用できる場合、__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__?のみをオーバーロードする場合に注意する必要がある予期しない落とし穴はありますか?

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

更新:クリストファーとして 指摘 、3.xではcmpが消えています。 上記の__cmp__と同じくらい簡単に比較を実装できる代替手段はありますか

95
Roger Pate

うん、それは例えば、の面ですべてを実装するのは簡単です__lt__とmixinクラス(またはメタクラス、または好みに応じてクラスデコレータを使用)。

例えば:

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から継承を乗算できます(必要に応じて他のベースがあれば)。クラスデコレータは非常に似ており、デコレートする新しいクラスの属性として同様の関数を挿入するだけです(実行時の結果は、メモリの観点から同等のコストで微視的に高速になる場合があります)。

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

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

しかし、上記のコードでは、< ;-)のみを使用するという心地よい対称性を維持したかったのです。 did__cmp__と友人を持っているので、なぜ__lt__を行かなければならないのかまったく同じものですか?すべてのPython=ランタイム(Classic、Jython、IronPython、PyPy、...)で非常に重荷です。間違いなくにバグがないコードそこにはないコードです-理想的にはタスクを実行するための1つの明白な方法があるべきであるというPythonの原則(CはISO標準の "Cpirit of C"セクションで同じ原則を持っています).

これは、物事を禁止するために邪魔にならないということではありません(たとえば、いくつかの用途でミックスインとクラスデコレータのほぼ同等)、それは間違いなくdoesは持ちたくないことを意味します正確に同じタスクを実行するための複数の同等のアプローチをサポートするためだけに冗長に存在するコンパイラおよび/またはランタイムのコードの周り。

さらに編集:実際には、質問へのコメントで述べたように、質問内のものを含む、多くのクラスに対して比較ANDハッシュを提供するより良い方法があります-__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__特殊メソッドは、直接必要なアドレスを指定します。

87
Alex Martelli

このケースを簡素化するために、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()))
45
jmagnusson
9
Christopher

(コメントを考慮するために17/6/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    
0
Gabriel Ferrer

Alex MartelliのComparableMixinKeyedMixinの回答に触発されて、次のmixinを思い付きました。 KeyedMixinと同様のキーベースの比較を使用する単一の_compare_to()メソッドを実装できますが、クラスは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
0
Eli Collins