属性によるオブジェクトインスタンスの同等性の比較


243

私はクラスの持つMyClass2つのメンバ変数が含まれている、foobar

class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

私はのために同じ値をそれぞれ有するこのクラスの2つのインスタンス、持っているfooとしますbar

x = MyClass('foo', 'bar')
y = MyClass('foo', 'bar')

ただし、等しいかどうかを比較すると、PythonはFalse次を返します。

>>> x == y
False

Pythonにこれら2つのオブジェクトを等しいと見なすにはどうすればよいですか?

回答:


353

メソッドを実装する必要があります__eq__

class MyClass:
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    def __eq__(self, other): 
        if not isinstance(other, MyClass):
            # don't attempt to compare against unrelated types
            return NotImplemented

        return self.foo == other.foo and self.bar == other.bar

これで出力されます:

>>> x == y
True

実装__eq__すると、自動的にクラスのインスタンスがハッシュ化できなくなります。つまり、インスタンスをセットやディクショナリに格納することはできません。不変タイプをモデル化していない場合(つまり、属性がfooありbar、オブジェクトの存続期間内に値が変更される可能性がある場合)、インスタンスをハッシュ化しないままにすることをお勧めします。

不変型をモデル化する場合は、datamodelフックも実装する必要があります__hash__

class MyClass:
    ...

    def __hash__(self):
        # necessary for instances to behave sanely in dicts and sets.
        return hash((self.foo, self.bar))

__dict__値をループして比較するという考えのような一般的な解決策はお勧めできません-には比較不可能な__dict__タイプまたはハッシュ不可能なタイプが含まれている可能性があるため、真に一般的なものになることはありません。

注意:Python 3より前では、の__cmp__代わりにを使用する必要がある場合があることに注意してください__eq__。Python 2 __ne__では、不等式の適切なデフォルト動作(つまり、等値結果の反転)がPython 2で自動的に作成されないため、Python 2ユーザーもを実装したい場合があります。


2
を使用するのではreturn NotImplementedなく、を使用することに興味がありましたNotImplementedError。ここではカバーされているトピックの:stackoverflow.com/questions/878943/...
init_js

48

オブジェクトの豊富な比較演算子をオーバーライドします。

class MyClass:
 def __lt__(self, other):
      # return comparison
 def __le__(self, other):
      # return comparison
 def __eq__(self, other):
      # return comparison
 def __ne__(self, other):
      # return comparison
 def __gt__(self, other):
      # return comparison
 def __ge__(self, other):
      # return comparison

このような:

    def __eq__(self, other):
        return self._id == other._id

3
以降はPython 2.5とでは、クラスが定義する必要があります__eq__()が、唯一の1 __lt__()__le__()__gt__()、または__ge__()それに加えて必要とされています。そのことから、Pythonは他のメソッドを推測できます。詳細についてはfunctools、を参照してください。
kba 2013年

1
@ kba、それは本当だとは思わない。これはfunctoolsモジュールでは機能しますが、標準のコンパレータでは機能しません。メソッドが実装されてMyObj1 != Myobj2いる場合にのみ機能し__ne__()ます。
2015年

6
functoolsに関する特定のチップは使用するべきである@functools.total_orderingその後、上記のように、あなただけ定義することができ、あなたのクラスにデコレータを__eq__し、他の1、残りは導出されます
Anentropic

7

__eq__クラスにメソッドを実装します。このようなもの:

def __eq__(self, other):
    return self.path == other.path and self.title == other.title

編集:オブジェクトに等しいインスタンスディクショナリがある場合に限り、オブジェクトを等しく比較する場合:

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

おそらくself is other、それらが同じオブジェクトであるかどうかを確認することを意味します。
S.Lott、2009

2
-1。これが2つの辞書インスタンスである場合でも、Pythonはそれらをキー/値によって自動的に比較します。これはJavaではありません...
e-satis 08

最初のソリューションを上げることができますAttributeError。行を挿入する必要がありますif hasattr(other, "path") and hasattr(other, "title"):(Pythonドキュメントのこの素晴らしい例のように)。
Maggyero

5

要約として:

  1. __eq__ではなく実装することをお勧めします__cmp__を実行する場合を除き、ます。Python<= 2.0(__eq__2.1で追加されました)
  2. また、実装することを忘れないでください__ne__return not self.__eq__(other)またはのようなものでなければなりません)return not self == other非常に特殊な場合を除い以外の)
  3. 演算子は、比較する各カスタムクラスに実装する必要があることを忘れないでください(以下の例を参照)。
  4. Noneになり得るオブジェクトと比較したい場合は、それを実装する必要があります。インタプリタはそれを推測できません...(下の例を参照)

    class B(object):
      def __init__(self):
        self.name = "toto"
      def __eq__(self, other):
        if other is None:
          return False
        return self.name == other.name
    
    class A(object):
      def __init__(self):
        self.toto = "titi"
        self.b_inst = B()
      def __eq__(self, other):
        if other is None:
          return False
        return (self.toto, self.b_inst) == (other.toto, other.b_inst)

2

特定のケースに応じて、次のことができます。

>>> vars(x) == vars(y)
True

参照オブジェクトのフィールドからPythonの辞書を


また興味深いことに、varsはdictを返しますが、視覚的に確認すると実際には等しいことが示されていても、unittestのassertDictEqualは機能していないようです。私は文字列にdictsを回す&それらを比較することによって、これを周りました:self.assertEqual(STR((tbl0)をvarsの)、STR(VARS(local_tbl0)))
ベン・

2

Python 3.7でのデータクラス(以上)、等価のオブジェクトインスタンスの比較は、内蔵機能です。

DataclassesバックポートはPython 3.6で利用できます。

(Py37) nsc@nsc-vbox:~$ python
Python 3.7.5 (default, Nov  7 2019, 10:50:52) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from dataclasses import dataclass
>>> @dataclass
... class MyClass():
...     foo: str
...     bar: str
... 
>>> x = MyClass(foo="foo", bar="bar")
>>> y = MyClass(foo="foo", bar="bar")
>>> x == y
True

Raymond Hettingerの2018 PyConプレゼンテーションは、Pythonデータクラスを使い始めるのに最適な方法です。
Sarath Chandra

1

オブジェクトのインスタンスを比較するときに、__cmp__関数が呼び出されます。

==演算子がデフォルトで__cmp__機能しない場合は、いつでもオブジェクトの関数を再定義できます。

編集:

指摘したように、この__cmp__関数は3.0以降廃止されました。代わりに、「リッチ比較」メソッドを使用する必要があります。


1
CMPの機能は3.0以降推奨されていません
クリストファー・

0

これを書いtest/utilsて、プロジェクトのモジュールに配置しました。クラスではない場合は、古い辞書を計画するだけで、これは両方のオブジェクトをトラバースし、

  1. すべての属性は対応する属性と等しい
  2. ダングリング属性は存在しません(1つのオブジェクトにのみ存在する属性)

その大きな...そのセクシーではない...しかし、ああボイはそれが機能します!

def assertObjectsEqual(obj_a, obj_b):

    def _assert(a, b):
        if a == b:
            return
        raise AssertionError(f'{a} !== {b} inside assertObjectsEqual')

    def _check(a, b):
        if a is None or b is None:
            _assert(a, b)
        for k,v in a.items():
            if isinstance(v, dict):
                assertObjectsEqual(v, b[k])
            else:
                _assert(v, b[k])

    # Asserting both directions is more work
    # but it ensures no dangling values on
    # on either object
    _check(obj_a, obj_b)
    _check(obj_b, obj_a)

を削除し、_assertプレーンなol 'を使用するだけで少しクリーンアップできassertますが、失敗したときに表示されるメッセージは役に立ちません。


0

メソッドを実装する必要があります__eq__

 class MyClass:
      def __init__(self, foo, bar, name):
           self.foo = foo
           self.bar = bar
           self.name = name

      def __eq__(self,other):
           if not isinstance(other,MyClass):
                return NotImplemented
           else:
                #string lists of all method names and properties of each of these objects
                prop_names1 = list(self.__dict__)
                prop_names2 = list(other.__dict__)

                n = len(prop_names1) #number of properties
                for i in range(n):
                     if getattr(self,prop_names1[i]) != getattr(other,prop_names2[i]):
                          return False

                return True

2
してくださいあなたの答えを編集し、あなたのコードにさらなる説明を追加し、それは10他の答えは異なっている理由を説明します。この質問は10年前のものであり、すでに認められた回答といくつかの非常に質の高い回答があります。追加の詳細がない場合、あなたの回答は他の回答に比べてはるかに品質が低く、おそらく反対投票または削除されます。
Das_Geek

0

以下は、2つのオブジェクト階層間で深い比較を行うことで(私の限定的なテストで)機能します。オブジェクト自体またはその属性が辞書である場合を含む、さまざまなケースを処理します。

def deep_comp(o1:Any, o2:Any)->bool:
    # NOTE: dict don't have __dict__
    o1d = getattr(o1, '__dict__', None)
    o2d = getattr(o2, '__dict__', None)

    # if both are objects
    if o1d is not None and o2d is not None:
        # we will compare their dictionaries
        o1, o2 = o1.__dict__, o2.__dict__

    if o1 is not None and o2 is not None:
        # if both are dictionaries, we will compare each key
        if isinstance(o1, dict) and isinstance(o2, dict):
            for k in set().union(o1.keys() ,o2.keys()):
                if k in o1 and k in o2:
                    if not deep_comp(o1[k], o2[k]):
                        return False
                else:
                    return False # some key missing
            return True
    # mismatched object types or both are scalers, or one or both None
    return o1 == o2

これは非常にトリッキーなコードなので、うまくいかない場合はコメントに追加してください。


0
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

    def __repr__(self):
        return str(self.value)

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

node1 = Node(1)
node2 = Node(1)

print(f'node1 id:{id(node1)}')
print(f'node2 id:{id(node2)}')
print(node1 == node2)
>>> node1 id:4396696848
>>> node2 id:4396698000
>>> True

0

あなたができない 1つ以上のクラスを扱っているなら内部から変更、これを行うには、diff固有のライブラリに依存しない一般的で簡単な方法があります。

非常に複雑なオブジェクトに対して安全で最も簡単な方法

pickle.dumps(a) == pickle.dumps(b)

picklePythonオブジェクト用の非常に一般的なシリアライゼーションライブラリであり、実際にはほとんどすべてをシリアライズできます。上記のスニペットではstr、シリアル化されたfromとfromのものを比較aしていますb。次のメソッドとは異なり、これにはカスタムクラスの型チェックの利点もあります。

最大の手間:特定の順序付けと[de / en]コーディングメソッドpickleにより、等しいオブジェクトに対して同じ結果が得られないことがあります。特に、頻繁に見られるような複雑なオブジェクト(ネストされたカスタムクラスインスタンスのリストなど)を扱う場合は特にそうです。一部のサードパーティライブラリで。そのような場合は、別の方法をお勧めします。

万物に安全な方法

シリアライズ可能なオブジェクトを提供する再帰的なリフレクションを記述して、結果を比較することができます

from collections.abc import Iterable

BASE_TYPES = [str, int, float, bool, type(None)]


def base_typed(obj):
    """Recursive reflection method to convert any object property into a comparable form.
    """
    T = type(obj)
    from_numpy = T.__module__ == 'numpy'

    if T in BASE_TYPES or callable(obj) or (from_numpy and not isinstance(T, Iterable)):
        return obj

    if isinstance(obj, Iterable):
        base_items = [base_typed(item) for item in obj]
        return base_items if from_numpy else T(base_items)

    d = obj if T is dict else obj.__dict__

    return {k: base_typed(v) for k, v in d.items()}


def deep_equals(*args):
    return all(base_typed(args[0]) == base_typed(other) for other in args[1:])

オブジェクトが何であるかは問題ではありませんが、深い平等が機能することが保証されています

>>> from sklearn.ensemble import RandomForestClassifier
>>>
>>> a = RandomForestClassifier(max_depth=2, random_state=42)
>>> b = RandomForestClassifier(max_depth=2, random_state=42)
>>> 
>>> deep_equals(a, b)
True

比較対象の数も重要ではありません

>>> c = RandomForestClassifier(max_depth=2, random_state=1000)
>>> deep_equals(a, b, c)
False

これの私のユースケースは、BDDテスト内ですでにトレーニングされた機械学習モデルの多様なセット間の深い同等性をチェックすることでした。モデルは、サードパーティのライブラリのさまざまなセットに属していました。確かに、__eq__ここで提案されている他の回答のように実装することは、私にとって選択肢ではありませんでした。

すべての拠点をカバー

比較されている1つ以上のカスタムクラスに実装がない__dict__シナリオが考えられます。これは決して一般的ではありませんが、sklearnのランダムフォレスト分類器内のサブタイプの場合です<type 'sklearn.tree._tree.Tree'>。ケースバイケースでこれらの状況を扱います。たとえば、具体的には、問題のタイプのコンテンツを、インスタンスに関する代表的な情報を提供するメソッド(この場合は__getstate__メソッド)のコンテンツに置き換えることにしました。そのため、最後から2番目の行base_typed

d = obj if T is dict else obj.__dict__ if '__dict__' in dir(obj) else obj.__getstate__()

編集:組織のために、私は最後の2行に置き換えbase_typedとしreturn dict_from(obj)、そしてそれがよりあいまいなLIBSに適応するために、本当に一般的な反射を(私は、あなたにDoc2Vecを探しています)実装しました

def isproperty(prop, obj):
    return not callable(getattr(obj, prop)) and not prop.startswith('_')


def dict_from(obj):
    """Converts dict-like objects into dicts
    """
    if isinstance(obj, dict):
        # Dict and subtypes are directly converted
        d = dict(obj)

    elif '__dict__' in dir(obj):
        d = obj.__dict__

    elif str(type(obj)) == 'sklearn.tree._tree.Tree':
        # Replaces sklearn trees with their state metadata
        d = obj.__getstate__()

    else:
        # Extract non-callable, non-private attributes with reflection
        kv = [(p, getattr(obj, p)) for p in dir(obj) if isproperty(p, obj)]
        d = {k: v for k, v in kv}

    return {k: base_typed(v) for k, v in d.items()}

上記のどのメソッドも、次のように、Trueキーと値のペアは同じであるがキーと値の順序が異なる、異なるオブジェクトに対して生成されないことに注意してください。

>>> a = {'foo':[], 'bar':{}}
>>> b = {'bar':{}, 'foo':[]}
>>> pickle.dumps(a) == pickle.dumps(b)
False

しかし、もしそうしたいのであれば、sortedとにかく前もってPythonの組み込みメソッドを使うことができます。


-1

属性ごとの比較を取得し、それが失敗するかどうか、およびどこで失敗するかを確認する場合は、次のリスト内包表記を使用できます。

[i for i,j in 
 zip([getattr(obj_1, attr) for attr in dir(obj_1)],
     [getattr(obj_2, attr) for attr in dir(obj_2)]) 
 if not i==j]

ここでのもう1つの利点は、PyCharmでデバッグするときに1行だけ絞り込んで、[式の評価]ウィンドウに入力できることです。


-3

最初の例(上記7を参照)を試しましたが、ipythonでは機能しませんでした。2つの同一のオブジェクトインスタンスを使用して実装した場合、cmp(obj1、obj2)は「1」を返すことに注意してください。奇妙なことに、属性値の1つを変更して再比較すると、cmp(obj1、obj2)を使用してオブジェクトが「1」を返し続けます。(はぁ...)

では、2つのオブジェクトを反復処理し、==記号を使用して各属性を比較する必要があります。


少なくともPython 2.7では、オブジェクトはデフォルトでIDによって比較されます。つまり、実用的な言葉でCPythonはメモリアドレスで比較します。そのため、cmp(o1、o2)は、「o1がo2」の場合にのみ0を返し、id(o1)およびid(o2)の値に応じて常に1または-1を返します
yacc143

-6

==と比較した場合のクラスのインスタンスは等しくありません。最善の方法は、cmp関数をクラスにアサインすることです。

コンテンツで比較したい場合は、単にcmp(obj1、obj2)を使用できます

あなたの場合、cmp(doc1、doc2)内容が同じである場合、-1を返します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.