[python] 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)

이 일을하는 더 우아한 방법을 알고 있습니까? 위 __dict__의 s 비교 방법을 사용하면 특별한 단점이 있습니까?

참고 : A는 설명의 비트 – 때 __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



답변

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

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

에서 파이썬이 항상 우선 기억 __ne__은 AS뿐만 아니라, 기능을 문서 상태 :

비교 연산자 사이에는 내재 된 관계가 없습니다. 진실은 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

파이썬 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로 선언 된곳. Python 3에는,또는로 선언 된 새로운 스타일의 클래스 만있습니다.class A(object)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 !=연산자 의 비전 역 문제를 해결하려면 피연산자 유형이 지원되지 않을 때 __eq____ne__메소드가 NotImplemented값을 반환해야합니다 . 문서는 정의 NotImplemented값을 :

제공된 피연산자에 대한 연산을 구현하지 않으면 숫자 메소드와 리치 비교 메소드가이 값을 리턴 할 수 있습니다. 그런 다음 통역사는 연산자에 따라 반영된 작업이나 다른 대체를 시도합니다. 참 값은 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

피연산자가 관련이없는 유형 (상속 없음) 인 경우 및 연산자의 정류 가 필요한 경우 새 스타일 클래스에 대해서도 NotImplemented값을 반환하는 False것이 옳은 일 입니다.==!=

우리는 아직있다? 좀 빠지는. 고유 번호는 몇 개입니까?

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

세트는 객체의 해시를 사용하며 기본적으로 파이썬은 객체 식별자의 해시를 반환합니다. 재정의 해 봅시다 :

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


답변

상속에주의해야합니다.

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

게다가, 당신의 접근 방식은 잘 작동 할 것입니다. 그것이 특별한 방법입니다.


답변

당신이 묘사하는 방식은 제가 항상했던 방식입니다. 완전히 일반적이므로 해당 기능을 항상 믹스 인 클래스로 분리하고 해당 기능을 원하는 클래스에서 상속 할 수 있습니다.

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


답변

직접적인 대답은 아니지만 때로는 약간의 장황한 테디 엄을 절약하기 때문에 압도적 인 것으로 보입니다. 문서에서 바로 자르십시오 …


functools.total_ordering (cls)

하나 이상의 풍부한 비교 순서 방법을 정의하는 클래스가 주어지면이 클래스 데코레이터가 나머지를 제공합니다. 이렇게하면 가능한 모든 풍부한 비교 연산을 지정하는 데 드는 노력이 단순화됩니다.

클래스 중 하나 정의해야합니다 __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()))


답변

당신은 모두를 오버라이드 (override) 할 필요는 없습니다 __eq__그리고 __ne__당신은 단지 오버라이드 할 수 __cmp__있지만, 이것은 ==,! ==, <,> 등의 결과에 시사점을 만들 것입니다.

is객체 식별 테스트 이는 a와 b가 모두 동일한 객체에 대한 참조를 보유하는 경우 isa가 b 임을 의미 True합니다. 파이썬에서는 항상 실제 객체가 아닌 변수의 객체에 대한 참조를 유지하므로 본질적으로 a가 b가되는 경우 객체의 객체는 동일한 메모리 위치에 있어야합니다. 이 행동을 어떻게 무시할 것인가?

편집 : __cmp__파이썬 3에서 제거되었다는 것을 몰랐 으므로 피하십시오.


답변

이 답변에서 : 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


답변

나는 당신이 찾고있는 두 용어가 평등 (==)과 정체성 (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