프로그래밍 언어/Python

Python 클래스에서 동등성 ( "equality")을 지원하는 방법

Rateye 2021. 8. 11. 11:21
728x90
반응형
질문 : Python 클래스에서 동등성 ( "equality")을 지원하는 우아한 방법

==!= 연산자를 사용하여 동등성을 허용하는 것이 종종 중요합니다. __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)

이 작업을 수행하는 더 우아한 방법을 알고 있습니까? __dict__ 를 비교하는 위의 방법을 사용하는 것에 대한 특별한 단점을 알고 있습니까?

참고 :이 설명의 비트 - 때 __eq____ne__ 정의되어 있지 않습니다이 동작을 확인할 수있는 것들 :

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

즉, a == b a is b 실제로 실행하기 때문에 False 평가됩니다 (예 : "Is a same object as b ?").

__eq____ne__ 가 정의되면 다음 동작을 찾을 수 있습니다 (우리가 추구하는 동작입니다).

>>> a = Foo(1)
>>> b = Foo(1)
>>> a is b
False
>>> a == b
True
답변

이 간단한 문제를 고려하십시오.

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에는 두 가지 종류의 클래스가 있습니다.

  • object 에서 상속 하지 않고 class A: , class A(): 또는 class A(B): 로 선언 클래식 스타일 (또는 구식) 클래스 : 여기서 B 는 클래식 스타일 클래스입니다.
  • 새로운 스타일 에서 상속 할 클래스, object 와 같은 선언 class A(object) 또는 class A(B): B 새로운 스타일의 클래스는. class 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 call n3.__eq__ ;
  • n1 != n3n3 != n1 n3.__ne__ 호출합니다.

의 비 교환 법칙 문제를 해결하려면 ==!= 파이썬이 고전적인 스타일 클래스의에 대한 사업자 __eq____ne__ 방법은 반환해야 NotImplemented 피연산자 유형이 지원되지 않을 때 값입니다. 문서 NotImplemented 값을 다음과 같이 정의합니다.

숫자 메서드 및 풍부한 비교 메서드는 제공된 피연산자에 대한 연산을 구현하지 않는 경우이 값을 반환 할 수 있습니다. (그런 다음 인터프리터는 연산자에 따라 반영된 작업 또는 다른 폴백을 시도합니다.) 진실 값은 참입니다.

이 경우 연산자는 비교 연산을 다른 피연산자 의 반영된 메서드 에 위임합니다. 문서 는 반영된 메소드를 다음과 같이 정의합니다.

이러한 메서드의 인수가 교체 된 버전은 없습니다 (왼쪽 인수가 작업을 지원하지 않지만 오른쪽 인수가 지원할 때 사용됨). 오히려, __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

복귀 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
출처 : https://stackoverflow.com/questions/390250/elegant-ways-to-support-equivalence-equality-in-python-classes
728x90
반응형