495

==カスタムクラスを作成する場合、 and!=演算子を使用して同等性を許可することが重要な場合がよくあります。Pythonでは、これはそれぞれ__eq____ne__特別なメソッドを実装することで可能になります。これを行うために私が見つけた最も簡単な方法は、次の方法です。

class Foo:
    def __init__(self, item):
        self.item = item

    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

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

これを行うためのよりエレガントな方法を知っていますか?上記のsを比較する方法を使用することの特定の欠点を知っています__dict__か?

:少し説明します。__eq____ne__が未定義の場合、次の動作が見られます。

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
False

つまり、実際に実行されるため、同一性のテスト(つまり、「同じオブジェクトは?」)にa == b評価されます。Falsea is bab

とが定義される__eq____ne__、次の動作が見つかります(これは私たちが求めているものです)。

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
4

11 に答える 11

417

この単純な問題を考えてみましょう:

class Number:

    def __init__(self, number):
        self.number = number


n1 = Number(1)
n2 = Number(1)

n1 == n2 # False -- oops

そのため、Python はデフォルトで比較操作にオブジェクト識別子を使用します。

id(n1) # 140400634555856
id(n2) # 140400634555920

関数をオーバーライド__eq__すると、問題が解決するようです。

def __eq__(self, other):
    """Overrides the default implementation"""
    if isinstance(other, Number):
        return self.number == other.number
    return False


n1 == n2 # True
n1 != n2 # True in Python 2 -- oops, False in Python 3

Python 2では、ドキュメントに記載されているように、常に__ne__関数もオーバーライドすることを忘れないでください。

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

def __ne__(self, other):
    """Overrides the default implementation (unnecessary in Python 3)"""
    return not self.__eq__(other)


n1 == n2 # True
n1 != n2 # False

Python 3では、ドキュメントに記載されているように、これは不要になりました。

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

しかし、それですべての問題が解決するわけではありません。サブクラスを追加しましょう:

class SubNumber(Number):
    pass


n3 = SubNumber(1)

n1 == n3 # False for classic-style classes -- oops, True for new-style classes
n3 == n1 # True
n1 != n3 # True for classic-style classes -- oops, False for new-style classes
n3 != n1 # False

注: Python 2 には 2 種類のクラスがあります。

  • から継承せず、 として宣言されているクラシック スタイル(または古いスタイル) クラス、またははクラシックスタイルのクラスです。objectclass A:class A():class A(B):B

  • から継承し、新しいスタイルのobjectクラスとして宣言されているclass A(object)新しいスタイルのクラス。Python 3 には、 、またはとして宣言された新しいスタイルのクラスしかありません。class A(B):Bclass A:class A(object):class A(B):

クラシック スタイルのクラスの場合、比較演算は常に最初のオペランドのメソッドを呼び出しますが、新しいスタイルのクラスの場合、オペランドの順序に関係なく、常にサブクラス オペランドのメソッドを呼び出します。

したがって、ここでNumberは、古典的なスタイルのクラスの場合:

  • n1 == n3呼び出しn1.__eq__ます。
  • n3 == n1呼び出しn3.__eq__ます。
  • n1 != n3呼び出しn1.__ne__ます。
  • n3 != n1を呼び出しますn3.__ne__

Numberが新しいスタイルのクラスの場合:

  • 両方n1 == n3n3 == n1呼び出しn3.__eq__ます。
  • 両方n1 != n3n3 != n1呼び出しますn3.__ne__

Python 2 クラシック スタイル クラスの==and演算子の非可換性の問題を修正するには、オペランド型がサポートされていない場合にandメソッドが値を返す必要があります。ドキュメントでは、値を次のように定義しています。!=__eq____ne__NotImplementedNotImplemented

数値メソッドと豊富な比較メソッドは、提供されたオペランドの演算を実装していない場合、この値を返すことがあります。(その後、インタープリターは、オペレーターに応じて、反映された操作またはその他のフォールバックを試みます。) その真偽値は true です。

この場合、演算子は比較操作を他のオペランドの反映されたメソッドに委譲します。ドキュメントでは、反映されたメソッドを次のように定義しています。

これらのメソッドには、引数を交換したバージョンはありません (左の引数が操作をサポートしていないが、右の引数が操作をサポートしている場合に使用されます)。むしろ、__lt__()__gt__()はお互いの反映であり、__le__()__ge__()はお互いの反映であり、 __eq__()__ne__()は自分自身の反映です。

結果は次のようになります。

def __eq__(self, other):
    """Overrides the default implementation"""
    if isinstance(other, Number):
        return self.number == other.number
    return NotImplemented

def __ne__(self, other):
    """Overrides the default implementation (unnecessary in Python 3)"""
    x = self.__eq__(other)
    if x is NotImplemented:
        return NotImplemented
    return not x

オペランドが関連のない型 (継承なし) の場合にand演算子の可換性NotImplementedが必要な場合は、新しいスタイルのクラスであっても、代わりに値を返すFalseことは正しいことです。==!=

私たちはまだそこにいますか?そうではありません。一意の番号はいくつありますか?

len(set([n1, n2, n3])) # 3 -- oops

セットはオブジェクトのハッシュを使用し、デフォルトでは Python はオブジェクトの識別子のハッシュを返します。それをオーバーライドしてみましょう:

def __hash__(self):
    """Overrides the default implementation"""
    return hash(tuple(sorted(self.__dict__.items())))

len(set([n1, n2, n3])) # 1

最終結果は次のようになります (検証のために最後にいくつかのアサーションを追加しました)。

class Number:

    def __init__(self, number):
        self.number = number

    def __eq__(self, other):
        """Overrides the default implementation"""
        if isinstance(other, Number):
            return self.number == other.number
        return NotImplemented

    def __ne__(self, other):
        """Overrides the default implementation (unnecessary in Python 3)"""
        x = self.__eq__(other)
        if x is not NotImplemented:
            return not x
        return NotImplemented

    def __hash__(self):
        """Overrides the default implementation"""
        return hash(tuple(sorted(self.__dict__.items())))


class SubNumber(Number):
    pass


n1 = Number(1)
n2 = Number(1)
n3 = SubNumber(1)
n4 = SubNumber(4)

assert n1 == n2
assert n2 == n1
assert not n1 != n2
assert not n2 != n1

assert n1 == n3
assert n3 == n1
assert not n1 != n3
assert not n3 != n1

assert not n1 == n4
assert not n4 == n1
assert n1 != n4
assert n4 != n1

assert len(set([n1, n2, n3, ])) == 1
assert len(set([n1, n2, n3, n4])) == 2
于 2014-08-07T07:24:42.003 に答える
220

継承には注意する必要があります。

>>> class Foo:
    def __eq__(self, other):
        if isinstance(other, self.__class__):
            return self.__dict__ == other.__dict__
        else:
            return False

>>> class Bar(Foo):pass

>>> b = Bar()
>>> f = Foo()
>>> f == b
True
>>> b == f
False

次のように、型をより厳密にチェックします。

def __eq__(self, other):
    if type(other) is type(self):
        return self.__dict__ == other.__dict__
    return False

それに加えて、あなたのアプローチはうまく機能します。それが特別な方法です。

于 2008-12-24T02:30:13.353 に答える
162

あなたが説明する方法は、私がいつもやってきた方法です。それは完全に汎用的であるため、いつでもその機能をミックスインクラスに分割し、その機能が必要なクラスに継承することができます。

class CommonEqualityMixin(object):

    def __eq__(self, other):
        return (isinstance(other, self.__class__)
            and self.__dict__ == other.__dict__)

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

class Foo(CommonEqualityMixin):

    def __init__(self, item):
        self.item = item
于 2008-12-24T00:44:13.270 に答える
17

直接的な答えではありませんが、場合によっては冗長な退屈を少し節約できるため、追加するのに十分な関連性があるように思われました。ドキュメントから直接カット...


functools.total_ordering(cls)

1 つ以上の豊富な比較順序付けメソッドを定義するクラスが与えられると、このクラス デコレータが残りを提供します。これにより、可能なすべてのリッチ比較操作を指定する作業が簡素化されます。

クラスは__lt__()__le__()__gt__()、またはのいずれかを定義する必要があります__ge__()。さらに、クラスは__eq__()メソッドを提供する必要があります。

バージョン 2.7 の新機能

@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()))
于 2012-09-19T12:19:09.897 に答える
9

両方をオーバーライドする必要はなく、オーバーライド__eq__する__ne__こともできます__cmp__が、これは ==、!==、< 、> などの結果に影響を与えます。

isオブジェクトの同一性をテストします。これは、 aと b の両方が同じオブジェクトへの参照を保持している場合にisa b になることを意味します。TruePython では、実際のオブジェクトではなく変数内のオブジェクトへの参照を常に保持するため、基本的に a is b が true になるには、オブジェクト内のオブジェクトが同じメモリ位置に配置されている必要があります。どのように、そして最も重要なことに、なぜこの動作をオーバーライドしようとしますか?

__cmp__編集: python 3から削除されたことを知らなかったので、避けてください。

于 2008-12-23T22:44:26.430 に答える
6

この回答から: https://stackoverflow.com/a/30676267/541136__ne__用語で定義するのは正しいですが、__eq__代わりに

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

あなたが使用する必要があります:

def __ne__(self, other):
    return not self == other
于 2015-06-17T00:30:12.723 に答える
4

あなたが探している2つの用語は平等(==)とアイデンティティ(is)だと思います。例えば:

>>> a = [1,2,3]
>>> b = [1,2,3]
>>> a == b
True       <-- a and b have values which are equal
>>> a is b
False      <-- a and b are not the same list object
于 2008-12-23T23:12:07.920 に答える
2

「is」テストは、組み込みの「id()」関数を使用して同一性をテストします。この関数は、本質的にオブジェクトのメモリ アドレスを返すため、オーバーロードできません。

ただし、クラスの等価性をテストする場合は、テストをもう少し厳密にして、クラスのデータ属性のみを比較する必要があります。

import types

class ComparesNicely(object):

    def __eq__(self, other):
        for key, value in self.__dict__.iteritems():
            if (isinstance(value, types.FunctionType) or 
                    key.startswith("__")):
                continue

            if key not in other.__dict__:
                return False

            if other.__dict__[key] != value:
                return False

         return True

このコードは、クラスの非関数データ メンバーのみを比較し、一般的に必要なプライベートなものをスキップします。Plain Old Python Objects の場合、__init__、__str__、__repr__、および __eq__ を実装する基本クラスがあるため、私の POPO オブジェクトは余分な (そしてほとんどの場合同一の) ロジックの負担を負いません。

于 2008-12-24T02:55:28.950 に答える