web-dev-qa-db-ja.com

Python、__ eq__に基づいて__ne __()演算子を実装する必要がありますか?

__eq__()演算子をオーバーライドしたいクラスがあります。 __ne__()演算子もオーバーライドする必要があるのは理にかなっているようですが、__ne__に基づいて__eq__を実装するのは理にかなっていますか?

class A:
    def __eq__(self, other):
        return self.value == other.value

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

または、Pythonがこれらの演算子を使用して、これは良い考えではない方法で欠けているものがありますか?

78
Falmarri

はい、それはまったく問題ありません。実際、 ドキュメント では、___ne___を定義するときに___eq___を定義するように促しています。

比較演算子間に暗黙の関係はありません。 _x==y_の真実は、_x!=y_が偽であることを意味するものではありません。したがって、__eq__()を定義するときは、演算子が期待どおりに動作するように__ne__()も定義する必要があります。

多くの場合(このような場合)、___eq___の結果を否定するのと同じくらい簡単ですが、常にではありません。

49
Daniel DiPaolo

短い答え:はい(ただし、ドキュメントを読んで正しく実行してください)

ShadowRangerの__ne__メソッドの実装は正しいものです(デフォルトのPython 3実装とまったく同じように動作するという意味で):

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

アーロンホールのnot self == otherメソッドの実装__ne__は、NotImplementednot NotImplementedFalse)を返せないため、正しくありません。したがって、優先度のある__ne__メソッドは、そうでない__ne__メソッドにフォールバックできません。優先順位があります。 not self == otherは、以前は__ne__メソッドのデフォルトのPython 3実装でしたが、バグであり、ShadowRangerが気づいたように、2015年1月のPython 3.4で修正されました( 問題#21408 )。

比較演算子の実装

PythonのPython言語リファレンス3つの chapter IIIデータモデルの状態

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

これらは、いわゆる「リッチ比較」方法です。演算子記号とメソッド名の対応は次のとおりです。x<y呼び出しx.__lt__(y)x<=y呼び出しx.__le__(y)x==y呼び出しx.__eq__(y)x!=y呼び出しx.__ne__(y)x>y呼び出しx.__gt__(y)、およびx>=yx.__ge__(y)を呼び出します。

リッチ比較メソッドは、引数の特定のペアに対する操作を実装しない場合、シングルトンNotImplementedを返すことがあります。

これらのメソッドの交換された引数バージョンはありません(左の引数は操作をサポートしないが、右の引数はサポートする場合に使用されます)。むしろ、__lt__()__gt__()はお互いの反射、__le__()__ge__()はお互いの反射、__eq__()__ne__()は独自の反射です。オペランドのタイプが異なり、右のオペランドのタイプが左のオペランドのタイプの直接または間接サブクラスである場合、右のオペランドのリフレクトされたメソッドが優先されます。それ以外の場合、左のオペランドのメソッドが優先されます。仮想サブクラスは考慮されません。

これをPythonコードに変換すると(operator_eqには==を、operator_neには!=を、operator_ltには<を、operator_gtには>を、operator_leには<=に、operator_geには>=を使用して)

def operator_eq(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__eq__(left)

        if result is NotImplemented:
            result = left.__eq__(right)
    else:
        result = left.__eq__(right)

        if result is NotImplemented:
            result = right.__eq__(left)

    if result is NotImplemented:
        result = left is right

    return result


def operator_ne(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ne__(left)

        if result is NotImplemented:
            result = left.__ne__(right)
    else:
        result = left.__ne__(right)

        if result is NotImplemented:
            result = right.__ne__(left)

    if result is NotImplemented:
        result = left is not right

    return result


def operator_lt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__gt__(left)

        if result is NotImplemented:
            result = left.__lt__(right)
    else:
        result = left.__lt__(right)

        if result is NotImplemented:
            result = right.__gt__(left)

    if result is NotImplemented:
        raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_gt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__lt__(left)

        if result is NotImplemented:
            result = left.__gt__(right)
    else:
        result = left.__gt__(right)

        if result is NotImplemented:
            result = right.__lt__(left)

    if result is NotImplemented:
        raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_le(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ge__(left)

        if result is NotImplemented:
            result = left.__le__(right)
    else:
        result = left.__le__(right)

        if result is NotImplemented:
            result = right.__ge__(left)

    if result is NotImplemented:
        raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_ge(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__le__(left)

        if result is NotImplemented:
            result = left.__ge__(right)
    else:
        result = left.__ge__(right)

        if result is NotImplemented:
            result = right.__le__(left)

    if result is NotImplemented:
        raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result

比較メソッドのデフォルト実装

ドキュメントには以下が追加されます。

デフォルトでは、__ne__()__eq__()に委任し、NotImplementedでない限り結果を反転します。比較演算子間に他の暗黙の関係はありません。たとえば、(x<y or x==y)の真実はx<=yを意味しません。

比較メソッドのデフォルト実装(__eq____ne____lt____gt____le__、および__ge__)は、次のように指定できます。

def __eq__(self, other):
    return NotImplemented

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

def __lt__(self, other):
    return NotImplemented

def __gt__(self, other):
    return NotImplemented

def __le__(self, other):
    return NotImplemented

def __ge__(self, other):
    return NotImplemented

したがって、これは__ne__メソッドの正しい実装です。また、__eq__メソッドがNotImplementedを返すとき、その逆__eq__Falsebool(NotImplemented)Trueであるため)常にnot NotImplementedメソッドの逆を返すとは限りません。 )目的のNotImplementedの代わりに。

__ne__の誤った実装

アーロンホールが上記で示したように、not self.__eq__(other)__ne__メソッドのデフォルトの実装ではありません。ただし、not self == other.も、デフォルト実装の動作とnot self == other実装の動作を2つのケースで比較することで、後者を以下に示します。

  • __eq__メソッドはNotImplemented;を返します。
  • __eq__メソッドは、NotImplementedとは異なる値を返します。

デフォルトの実装

A.__ne__メソッドがデフォルトの実装を使用し、A.__eq__メソッドがNotImplementedを返すとどうなるか見てみましょう。

class A:
    pass


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) == "B.__ne__"
  1. !=A.__ne__を呼び出します。
  2. A.__ne__A.__eq__を呼び出します。
  3. A.__eq__NotImplementedを返します。
  4. !=B.__ne__を呼び出します。
  5. B.__ne__"B.__ne__"を返します。

これは、A.__eq__メソッドがNotImplementedを返すと、A.__ne__メソッドがB.__ne__メソッドにフォールバックすることを示しています。

ここで、A.__ne__メソッドがデフォルトの実装を使用し、A.__eq__メソッドがNotImplementedとは異なる値を返す場合に何が起こるかを見てみましょう。

class A:

    def __eq__(self, other):
        return True


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=A.__ne__を呼び出します。
  2. A.__ne__A.__eq__を呼び出します。
  3. A.__eq__Trueを返します。
  4. !=not True、つまりFalseを返します。

これは、この場合、A.__ne__メソッドがA.__eq__メソッドの逆を返すことを示しています。したがって、__ne__メソッドは、ドキュメントで宣伝されているように動作します。

A.__ne__メソッドのデフォルトの実装を上記の正しい実装でオーバーライドすると、同じ結果が得られます。

not self == otherの実装

A.__ne__メソッドのデフォルトの実装をnot self == other実装でオーバーライドし、A.__eq__メソッドがNotImplementedを返すとどうなるか見てみましょう。

class A:

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


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is True
  1. !=A.__ne__を呼び出します。
  2. A.__ne__==を呼び出します。
  3. ==A.__eq__を呼び出します。
  4. A.__eq__NotImplementedを返します。
  5. ==B.__eq__を呼び出します。
  6. B.__eq__NotImplementedを返します。
  7. ==A() is B()、つまりFalseを返します。
  8. A.__ne__not False、つまりTrueを返します。

__ne__メソッドのデフォルト実装は、Trueではなく、"B.__ne__"を返しました。

次に、A.__ne__メソッドのデフォルト実装をnot self == other実装でオーバーライドし、A.__eq__メソッドがNotImplementedとは異なる値を返す場合に何が起こるかを見てみましょう。

class A:

    def __eq__(self, other):
        return True

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


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=A.__ne__を呼び出します。
  2. A.__ne__==を呼び出します。
  3. ==A.__eq__を呼び出します。
  4. A.__eq__Trueを返します。
  5. A.__ne__not True、つまりFalseを返します。

__ne__メソッドのデフォルト実装も、この場合Falseを返しました。

この実装は、__ne__メソッドがNotImplementedを返すときに、__eq__メソッドのデフォルト実装の動作の複製に失敗するため、正しくありません。

2
Maggyero