속성별로 객체 인스턴스가 동일한 지 비교


244

나는 수업이 MyClass두 멤버 변수가 포함되어, foobar:

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

나는에 대해 동일한 값을 가지고 각각의이 클래스의 두 인스턴스가 foo와를 bar:

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

그러나 동등성을 비교하면 Python은 False다음을 반환합니다 .

>>> x == y
False

파이썬 이이 두 객체를 동등하게 생각하게하려면 어떻게해야합니까?

답변:


354

메소드를 구현해야합니다 __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__하면 클래스의 인스턴스를 자동으로 해시 할 수 없게되므로 세트와 딕트에 저장할 수 없습니다. 불변 유형을 모델링하지 않는 경우 (예 : 속성 foobar 개체의 수명 내에서 값을 변경할 수 있음), 다음은 그냥 unhashable로 인스턴스를두고하는 것이 좋습니다.

불변 유형을 모델링하는 경우 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__ 비교할 수 없거나 해시 할 수없는 유형이 포함되어 .

NB : Python 3 이전에는 __cmp__대신 을 사용해야 할 수도 있습니다 __eq__. 파이썬 2 사용자는 __ne__불평등에 대한 합리적인 기본 행동 (즉, 평등 결과 반전)이 파이썬 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
파이썬 2.5과 이후, 클래스 정의해야합니다 __eq__(),하지만 단 하나의 __lt__(), __le__(), __gt__(), 또는 __ge__()그 외에 필요합니다. 그로부터 파이썬은 다른 방법을 유추 할 수 있습니다. 자세한 내용 functools은 참조하십시오.
KBA

1
@kba, 나는 그것이 사실이라고 생각하지 않습니다. 이것은 functools모듈에서는 작동 하지만 표준 비교기 에서는 작동하지 않습니다 . 메소드가 구현 된 MyObj1 != Myobj2경우에만 작동합니다 __ne__().
Arel

6
functools에 대한 구체적인 팁이 사용되어야한다 @functools.total_ordering그냥 정의 할 수 있습니다 위와 같이 다음 클래스에 장식을 __eq__한 다른 나머지는 파생됩니다
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

2
-1. 이것이 두 개의 사전 인스턴스 일지라도 파이썬은 키 / 값으로 자동으로 비교합니다. 이것은 자바가 아닙니다 ...
전자 위성

첫 번째 솔루션은을 올릴 수 있습니다 AttributeError. 라인을 삽입해야합니다 if hasattr(other, "path") and hasattr(other, "title"):( Python 문서 의이 좋은 예같습니다 ).
Maggyero

5

요약하자면 :

  1. python <= 2.0을 실행하는 경우를 제외하고 는 __eq__오히려 구현 하는 것이 좋습니다.__cmp____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

객체의 필드에서 파이썬 사전 보기


또한 흥미로운 점은 vars가 dict를 반환하지만 시각적 검토 결과 실제로는 같음에도 불구하고 unittest의 assertDictEqual이 작동하지 않는 것 같습니다. 나는 dicts를 문자열로 바꾸고 그것들을 비교함으로써이 문제를 해결했다 : self.assertEqual (str (vars (tbl0)), str (vars (local_tbl0)))
Ben

2

Python 3.7의 데이터 클래스 사용 (위), 어떤지 객체 인스턴스의 비교는 붙박이 기능이다.

Dataclasses에 대한 백 포트는 파이썬 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 Dataclasses를 시작하는 훌륭한 방법입니다.
사라 스 찬드라

1

객체의 인스턴스를 비교할 때 __cmp__함수가 호출됩니다.

== 연산자가 기본적으로 작동하지 않으면 항상 __cmp__객체 의 기능을 재정의 할 수 있습니다 .

편집하다:

지적한 바와 같이이 __cmp__기능은 3.0부터 사용되지 않습니다. 대신 "풍부한 비교" 방법을 사용해야합니다 .


1
CMP의 기능은 3.0 이상 사용되지 않습니다
크리스토퍼

1

내부에서 변경할 수없는 하나 이상의 클래스를 다루는 경우 diff 관련 라이브러리에 의존하지 않는 일반적이고 간단한 방법이 있습니다.

가장 쉽고 안전하지 않은 복잡한 객체 방법

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

pickle파이썬 객체를위한 매우 일반적인 직렬화 라이브러리이므로, 거의 모든 것을 직렬화 할 수 있습니다. 위의 스 니펫 str에서 from을 serialized a와 from을 비교합니다 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__ 다른 답변과 같이 하면 옵션이 아니 었습니다.

모든 기초를 덮고

비교중인 하나 이상의 사용자 정의 클래스에 구현 이없는__dict__ 시나리오에있을 수 있습니다 . 그것은 흔하지는 않지만 sklearn의 Random Forest 분류 자 ​​내의 하위 유형의 경우입니다 <type 'sklearn.tree._tree.Tree'>. 사례별로 이러한 상황을 치료 - 예 특히 , 내가 나에게 (이 경우에는 인스턴스에 대한 대표적인 정보를 제공하는 방법의 내용으로 고통받는 유형의 내용을 교체하기로 결정 __getstate__방법). 그런 이유로 두 번째에서 마지막 행 base_typed

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

편집 : 조직을 위해 마지막 두 줄을 base_typedreturn dict_from(obj)바꾸고 더 모호한 라이브러리를 수용하기 위해 실제로 일반적인 반영을 구현했습니다 (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어쨌든 파이썬의 내장 메소드를 사용할 수 있습니다 .


0

나는 이것을 쓰고 test/utils내 프로젝트 의 모듈에 넣었다 . 수업이 아닌 경우 계획을 세우면 두 객체를 모두 통과하고

  1. 모든 속성은 해당 속성과 동일합니다
  2. 매달려있는 속성이 없습니다 (한 개체에만 존재하는 공격자)

그것의 큰 ... 그것의 섹시 하지 않다 ...하지만 오보이 작동합니까!

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 살 이며 이미 받아 들여진 답변과 여러 개의 고품질 답변이 있습니다. 추가 세부 정보가 없으면 답변이 다른 사람에 비해 품질이 훨씬 떨어지며 다운 보트 또는 삭제 될 가능성이 높습니다.
Das_Geek

0

아래는 두 객체 계층 사이에서 심도있는 비교를 수행하여 제한된 테스트에서 작동합니다. 객체 자체 또는 속성이 사전 인 경우를 포함하여 다양한 경우를 처리합니다.

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

-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]

PyCharm에서 디버깅 할 때 한 줄을 누르고 "표현식 평가"창에 입력 할 수 있다는 장점이 있습니다.


-3

초기 예제를 시도했지만 (위 7 참조) ipython에서는 작동하지 않습니다. cmp (obj1, obj2)는 ​​두 개의 동일한 객체 인스턴스를 사용하여 구현 된 경우 "1"을 반환합니다. 이상하게도 속성 값 중 하나를 수정하고 다시 비교할 때 cmp (obj1, obj2)를 사용하여 개체가 계속 "1"을 반환합니다. (한숨...)

좋아, 그래서 당신이해야 할 일은 두 개의 객체를 반복하고 == 부호를 사용하여 각 속성을 비교하는 것입니다.


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.