파이썬, __eq__를 기반으로 __ne __ () 연산자를 구현해야합니까?


98

__eq__()연산자 를 재정의하려는 클래스가 있습니다 . __ne__()연산자도 재정의해야한다는 것이 이해가되는 것 같지만 , __ne__기반으로 구현하는 것이 합리적입니까?__eq__ 입니까?

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

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

아니면 파이썬이 이러한 연산자를 사용하는 방식에서 빠진 것이 있는데 이것이 좋은 생각이 아닙니다.

답변:


57

예, 완벽합니다. 실제로 문서 에서는 다음을 정의 __ne__할 때 정의하도록 촉구합니다 __eq__.

비교 연산자 간에는 암시 적 관계가 없습니다. 의 진실은 x==y그것이 x!=y 거짓 임을 의미하지 않습니다 . 따라서 정의 할 때 연산자가 예상대로 작동하도록 __eq__()정의 __ne__()해야합니다.

많은 경우 (예 :이 경우)의 결과를 부정하는 것처럼 간단 __eq__하지만 항상 그런 것은 아닙니다.


12
이것이 정답입니다 (아래 @ aaron-hall). 인용 한 문서는을 사용하여 구현하도록 권장 하지 않으며 구현 __ne__하는 __eq__것만 권장 합니다.
guyarad

2
@guyarad : 실제로 Aaron의 대답은 제대로 위임하지 않았기 때문에 여전히 약간 잘못되었습니다. 대신 치료의 NotImplemented위임에 큐와 같은 하나 개의 측면에서 수익을 __ne__다른 측면에서, not self == other(피연산자의이 가정되는 __eq__암시 적으로 위임 다른 피연산자를 비교하는 방법을 알고하지 않습니다) __eq__다음 반전, 다른 측면에서. SQLAlchemy ORM의 필드와 같은 이상한 유형의 경우 이로 인해 문제가 발생 합니다.
ShadowRanger

1
ShadowRanger의 비판은 매우 병리학 적 사례 (IMHO)에만 적용되며 아래 답변에서 완전히 다룹니다.
Aaron Hall

1
최신 문서 (적어도 3.7의 경우 이전 버전 일 수 있음)는 __ne__자동으로 위임 __eq__되며이 답변의 인용문은 더 이상 문서에 없습니다. 결론적으로 구현 __eq__하고 __ne__위임 하는 것만이 완벽하게 비단뱀 적 입니다.
bluesummers

132

Python, __ne__()기반으로 연산자를 구현해야 __eq__합니까?

짧은 답변 : 구현하지 마십시오.하지만 필요한 경우 사용 ==하지 말고__eq__

Python 3에서는 기본적으로 !=의 부정이므로을 ==작성할 필요도 __ne__없으며 문서는 더 이상 작성에 대한 의견이 없습니다.

일반적으로 Python 3 전용 코드의 경우 내장 객체와 같은 부모 구현을 가리지 않으면 코드를 작성하지 마십시오.

즉, Raymond Hettinger의 의견 을 염두에 두십시오 .

__ne__메서드 는 수퍼 클래스에 아직 정의되지 않은 __eq__경우에만 자동으로 따릅니다 __ne__. 따라서 내장에서 상속하는 경우 둘 다 재정의하는 것이 가장 좋습니다.

Python 2에서 작동하는 코드가 필요한 경우 Python 2에 대한 권장 사항을 따르십시오. 그러면 Python 3에서 정상적으로 작동합니다.

파이썬 2에서 파이썬 자체가 자동으로 다른 측면에서 어떤 동작을 구현하지 않습니다 - 따라서를 정의해야 __ne__측면에서 ==대신의 __eq__. EG

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

    def __ne__(self, other):
        return not self == other # NOT `return not self.__eq__(other)`

증거보기

  • 구현 __ne__()__eq__및 기반 연산자
  • __ne__Python 2에서 전혀 구현하지 않음

아래 데모에서 잘못된 동작을 제공합니다.

긴 답변

문서 파이썬 2는 말합니다 :

비교 연산자 간에는 암시 적 관계가 없습니다. 의 진실은 x==y그것이 x!=y거짓 임을 의미하지 않습니다 . 따라서 정의 할 때 연산자가 예상대로 작동하도록 __eq__()정의 __ne__()해야합니다.

즉 , __ne__의 역으로 정의 __eq__하면 일관된 동작을 얻을 수 있습니다.

이 문서 섹션은 Python 3 용으로 업데이트되었습니다 .

기본적 으로는를 __ne__()위임 __eq__()하고 결과가 아닌 경우 결과를 반전합니다 NotImplemented.

과에서 "새로운 기능"섹션 , 우리는이 동작이 변경된 참조 :

  • !=이제를 반환 ==하지 않는 ==한의 반대를 반환합니다 NotImplemented.

구현__ne__== 을 위해 __eq__메서드를 직접 사용하는 대신 연산자 를 사용하여self.__eq__(other) 하위 클래스가 NotImplemented검사 된 유형에 대해 반환 하면 Python이 other.__eq__(self) 문서에서 다음 을 적절하게 검사 합니다 .

그만큼 NotImplemented객체

이 유형에는 단일 값이 있습니다. 이 값을 가진 단일 개체가 있습니다. 이 개체는 기본 제공 이름을 통해 액세스됩니다. NotImplemented . 숫자 메서드 및 풍부한 비교 메서드는 제공된 피연산자에 대한 연산을 구현하지 않는 경우이 값을 반환 할 수 있습니다. (그런 다음 인터프리터는 연산자에 따라 반영된 작업 또는 다른 폴백을 시도합니다.) 진실 값은 참입니다.

그들은, 파이썬 검사 동일한 형태가 아닌 경우 풍부한 비교 연산자가 주어진 경우,이 경우 other서브 타입이며,이 정의 된 연산자를 갖는 경우, 사용 other(위한 역 제의 방법을 <, <=, >=>). 경우 NotImplemented반환, 다음 은 그 반대의 방법을 사용합니다. ( 동일한 방법을 두 번 확인 하지 않습니다 .) ==연산자를 사용하면이 논리가 발생할 수 있습니다.


기대

__ne__클래스의 사용자는 A.의 모든 인스턴스에 대해 다음 함수가 동일 할 것으로 기대하기 때문에 의미 상 동등성 검사 측면에서 구현해야합니다 .

def negation_of_equals(inst1, inst2):
    """always should return same as not_equals(inst1, inst2)"""
    return not inst1 == inst2

def not_equals(inst1, inst2):
    """always should return same as negation_of_equals(inst1, inst2)"""
    return inst1 != inst2

즉, 위의 두 함수는 항상 동일한 결과를 반환 해야합니다 . 그러나 이것은 프로그래머에 따라 다릅니다.

다음을 __ne__기반으로 정의 할 때 예상치 못한 동작의 시연 __eq__:

먼저 설정 :

class BaseEquatable(object):
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        return isinstance(other, BaseEquatable) and self.x == other.x

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

class ComparableRight(BaseEquatable):
    def __ne__(self, other):
        return not self == other

class EqMixin(object):
    def __eq__(self, other):
        """override Base __eq__ & bounce to other for __eq__, e.g. 
        if issubclass(type(self), type(other)): # True in this example
        """
        return NotImplemented

class ChildComparableWrong(EqMixin, ComparableWrong):
    """__ne__ the wrong way (__eq__ directly)"""

class ChildComparableRight(EqMixin, ComparableRight):
    """__ne__ the right way (uses ==)"""

class ChildComparablePy3(EqMixin, BaseEquatable):
    """No __ne__, only right in Python 3."""

동일하지 않은 인스턴스를 인스턴스화합니다.

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)

예상되는 동작 :

(참고 : 아래 각 항목의 모든 두 번째 주장은 동일하므로 이전 항목과 논리적으로 중복되지만 하나가 다른 항목의 하위 클래스 일 때 순서가 중요하지 않음 을 보여주기 위해 포함합니다 . )

이러한 인스턴스는 다음으로 __ne__구현되었습니다 ==.

assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1

Python 3에서 테스트하는 이러한 인스턴스도 올바르게 작동합니다.

assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1

그리고 이것들이 다음과 같이 __ne__구현 되었음을 __eq__상기하십시오. 이것은 예상되는 동작이지만 구현은 올바르지 않습니다.

assert not wrong1 == wrong2         # These are contradicted by the
assert not wrong2 == wrong1         # below unexpected behavior!

예상치 못한 동작 :

이 비교는 위의 비교 ( not wrong1 == wrong2) 와 모순 됩니다.

>>> assert wrong1 != wrong2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

과,

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

__ne__Python 2에서 건너 뛰지 마십시오.

__ne__Python 2에서 구현 을 건너 뛰지 않아야한다는 증거는 다음과 같은 동등한 객체를 참조하세요.

>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True

위의 결과는 False!

Python 3 소스

에 대한 기본 CPython 구현 __ne__은 다음 위치 typeobject.c에 있습니다object_richcompare .

case Py_NE:
    /* By default, __ne__() delegates to __eq__() and inverts the result,
       unless the latter returns NotImplemented. */
    if (Py_TYPE(self)->tp_richcompare == NULL) {
        res = Py_NotImplemented;
        Py_INCREF(res);
        break;
    }
    res = (*Py_TYPE(self)->tp_richcompare)(self, other, Py_EQ);
    if (res != NULL && res != Py_NotImplemented) {
        int ok = PyObject_IsTrue(res);
        Py_DECREF(res);
        if (ok < 0)
            res = NULL;
        else {
            if (ok)
                res = Py_False;
            else
                res = Py_True;
            Py_INCREF(res);
        }
    }
    break;

하지만 기본값 __ne____eq__?

더 높은 수준 ( PyObject_RichCompare )이 덜 효율적 이기 때문에 __ne__C 수준에서 Python 3의 기본 구현 세부 정보를 사용 __eq__하므로 처리해야합니다.==NotImplemented .

경우 __eq__올바르게 구현, 다음의 부정은 ==또한 올 - 그것은 우리가 우리의 낮은 수준의 구현 세부 사항을 피할 수 있습니다 __ne__.

사용은 ==우리의 낮은 수준의 논리를 유지하기 위해 우리를 허용 곳, 그리고 주소 NotImplemented__ne__.

==반환 될 수 있다고 잘못 가정 할 수 있습니다 NotImplemented.

실제로 __eq__ID를 확인하는 의 기본 구현과 동일한 논리를 사용합니다 (아래의 do_richcompare 및 증거 참조).

class Foo:
    def __ne__(self, other):
        return NotImplemented
    __eq__ = __ne__

f = Foo()
f2 = Foo()

그리고 비교 :

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True

공연

내 말을 믿지 말고 더 성능이 좋은 것을 보자.

class CLevel:
    "Use default logic programmed in C"

class HighLevelPython:
    def __ne__(self, other):
        return not self == other

class LowLevelPython:
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

def c_level():
    cl = CLevel()
    return lambda: cl != cl

def high_level_python():
    hlp = HighLevelPython()
    return lambda: hlp != hlp

def low_level_python():
    llp = LowLevelPython()
    return lambda: llp != llp

이러한 성능 수치가 스스로를 대변한다고 생각합니다.

>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029

이것은 low_level_python그렇지 않으면 C 레벨에서 처리 될 파이썬에서 로직을 수행 한다고 생각할 때 의미가 있습니다 .

일부 비평가에 대한 응답

다른 답변자는 다음과 같이 씁니다.

Aaron Hall의 메서드 구현 not self == other은 ( is ) __ne__절대 반환 할 수 없기 때문에 올바르지 않으므로 우선 순위가 있는 메서드는 우선 순위가없는 메서드로 대체 될 수 없습니다 .NotImplementednot NotImplementedFalse__ne____ne__

__ne__결코 돌아 가기 NotImplemented가 잘못된하지 않습니다. 대신 NotImplemented.NET과의 동등성 검사를 통해 우선 순위를 처리 ==합니다. ==올바르게 구현 되었다고 가정하면 완료됩니다.

not self == other이전에는 __ne__메서드 의 기본 Python 3 구현 이었지만 버그였으며 ShadowRanger가 발견 한대로 2015 년 1 월 Python 3.4에서 수정되었습니다 (문제 # 21408 참조).

글쎄, 이것을 설명합시다.

앞서 언급했듯이 Python 3은 기본적 __ne__으로 먼저 self.__eq__(other)반환 여부 NotImplemented(싱글 톤)를 확인하여 처리합니다. 이 경우 확인하고 반환 해야합니다 is. 그렇지 않으면 역을 반환해야합니다. 다음은 클래스 믹스 인으로 작성된 논리입니다.

class CStyle__ne__:
    """Mixin that provides __ne__ functionality equivalent to 
    the builtin functionality
    """
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

이것은 C 레벨 Python API의 정확성을 위해 필요하며 Python 3에서 도입되어

불필요한. __ne__자체 검사를 구현하는 방법 과 __eq__직접 또는 통해 위임하는 방법을 포함하여 모든 관련 방법이 제거 되었으며 가장 일반적인 방법이었습니다.====

대칭이 중요합니까?

우리의 끊임없는 비평가는 무엇보다 대칭을 중요시 하는 NotImplemented에서 취급하는 사례를 만드는 병리학적인 예를 제공합니다 __ne__. 명확한 예를 들어 스틸 맨의 주장을 들어 보자.

class B:
    """
    this class has no __eq__ implementation, but asserts 
    any instance is not equal to any other object
    """
    def __ne__(self, other):
        return True

class A:
    "This class asserts instances are equivalent to all other objects"
    def __eq__(self, other):
        return True

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, False, True)

따라서이 논리에 의해 대칭을 유지하려면 __ne__Python 버전에 관계없이 복잡한을 작성해야합니다 .

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return True
    def __ne__(self, other):
        result = other.__eq__(self)
        if result is NotImplemented:
            return NotImplemented
        return not result

>>> A() == B(), B() == A(), A() != B(), B() != A()
(True, True, True, True)

분명히 우리는 이러한 인스턴스가 동일하고 동일하지 않다는 사실을 신경 쓰지 말아야합니다.

나는 대칭이 합리적인 코드의 가정과 문서의 조언을 따르는 것보다 덜 중요하다고 제안합니다.

그러나 A가를 현명하게 구현 __eq__했다면 여기에서 내 방향을 따를 수 있고 여전히 대칭을 가질 수 있습니다.

class B:
    def __ne__(self, other):
        return True

class A:
    def __eq__(self, other):
        return False         # <- this boolean changed... 

>>> A() == B(), B() == A(), A() != B(), B() != A()
(False, False, True, True)

결론

Python 2 호환 코드의 경우를 사용 ==하여 __ne__. 더 많은 것 :

  • 옳은
  • 단순한
  • 공연자

Python 3에서만 C 수준에서 저수준 부정을 사용합니다. 훨씬 간단하고 성능이 뛰어납니다 (프로그래머가 올바른지 판단 할 책임이 있습니다 ).

다시 말하지만, 고수준 Python에서 저수준 논리를 작성 하지 마십시오 .


3
훌륭한 예! 놀랍게도 피연산자의 순서는 "오른쪽"반사가있는 일부 매직 메서드와 달리 피연산자의 순서 가 전혀 중요하지 않다는 것 입니다. 내가 놓친 부분 (그리고 많은 시간이 소요되는 부분)을 다시 반복하려면 : 코드에 수퍼 클래스가 있는지 연산자 왼쪽에 서브 클래스가 있는지 여부에 관계없이 하위 클래스 의 풍부한 비교 방법이 먼저 시도됩니다. 이것이 당신이 a1 != c2반환 한 이유입니다 False.--- 실행되지 a1.__ne__않았지만 mixin의 방법 c2.__ne__을 부정했습니다 . 이후 truthy이다, 입니다 . __eq__NotImplementednot NotImplementedFalse
케빈 J. 체이스

2
귀하의 최근 업데이트는의 성능 이점을 성공적으로 보여 주 not (self == other)었지만 아무도 빠르지 않다고 주장하지 않습니다 (음, 어쨌든 Py2의 다른 옵션보다 빠름). 문제는 어떤 경우에는 잘못되었다는 것 입니다. 파이썬 자체가 예전에는 그렇게 not (self == other)했지만, 임의의 서브 클래스가있을 때 잘못 되었기 때문에 변경되었습니다 . 오답에 대한 가장 빠른 것은 여전히 잘못되었습니다 .
ShadowRanger

1
구체적인 예는 정말 중요하지 않습니다. 문제는 구현에서 __ne__델리게이트 의 동작 __eq__(필요한 경우 양쪽 모두)이지만, 둘 다 "포기" 하더라도 다른 쪽 의 동작 으로 절대 돌아 가지 않는다는 것 입니다. 올바른 델리게이트가 자신 에게 반환 되면 다른 쪽을 반전하는 대신 다른 쪽의으로 돌아가도록 폴백합니다 (다른 쪽이 에 위임하도록 명시 적으로 옵트 인하지 않았을 수 있으므로 그렇게 하지 않아야합니다. 그 결정을 내릴 것입니다). __ne____eq____ne__ __eq__NotImplemented__ne____eq____eq__
ShadowRanger

1
@AaronHall : 오늘 이것을 재검토 할 때, 나는 당신의 구현이 일반적으로 하위 클래스에 문제가 있다고 생각 하지 않습니다 (깨지기 위해 극도로 복잡 할 것이고, 부모에 대해 완전히 알고 있다고 가정하는 하위 클래스는 그것을 피할 수 있어야합니다 ). 그러나 나는 내 대답에 복잡하지 않은 예를 들었습니다. 비 병적 인 경우는 SQLAlchemy의의 ORM,도 아니다 __eq____ne__반환하거나 True또는 False아니라 ( "truthy"될 일) 프록시 객체입니다. 잘못 구현 __ne__하면 비교에 순서가 중요합니다 (한 번의 순서로만 프록시를 얻음).
ShadowRanger

1
명확하게 말하면 99 % (또는 99.999 %)의 경우 솔루션이 괜찮고 (분명히) 더 빠릅니다. 그러나 다른 사람이 코드를 사용할 수있는 라이브러리 작성자로서 좋지 않은 경우를 제어 할 수 없기 때문에 (읽기 : 개인적인 용도로만 사용하는 간단한 일회성 스크립트 및 모듈을 제외한 모든 것) 올바른 구현을 사용하여 연산자 오버로딩에 대한 일반 계약을 준수하고 발생할 수있는 다른 코드로 작업하십시오. 다행히 Py3에서는 __ne__완전히 생략 할 수 있기 때문에이 모든 것이 중요하지 않습니다 . 지금부터 1 년 후에 Py2는 죽을 것이고 우리는 이것을 무시합니다. :-)
ShadowRanger

10

기록을 위해 정규적으로 정확하고 교차하는 Py2 / Py3 휴대용 장치 __ne__는 다음과 같습니다.

import sys

class ...:
    ...
    def __eq__(self, other):
        ...

    if sys.version_info[0] == 2:
        def __ne__(self, other):
            equal = self.__eq__(other)
            return equal if equal is NotImplemented else not equal

이것은 __eq__정의 할 수 있는 모든 항목에서 작동 합니다.

  • 달리 not (self == other)관련된 클래스 중 하나의 결과는 것을 의미하지 않는 경우, 비교를 포함하는 몇 가지 성가신 / 복잡한 경우에 간섭하지 않는 __ne__결과와 동일한 not__eq__(예를 들면 SQLAlchemy의의 ORM, 모두 __eq____ne__특별한 프록시 객체 반환 아니 True또는 False, not결과를 시도하면 __eq__반환됩니다.False 하지 않고) 올바른 프록시 객체보다.
  • 와 달리 not self.__eq__(other), 이것은 반환 __ne__할 때 다른 인스턴스 의 에 올바르게 위임 self.__eq__합니다 NotImplemented( not self.__eq__(other)사실이기 때문에 추가로 잘못 될 수 NotImplemented있으므로 __eq__비교를 수행하는 방법을 몰랐을 때를 __ne__반환합니다 False. 이는 실제로 유일한 경우 두 객체가 동일하다는 것을 의미합니다. 요청 된 개체는 전혀 모름, 같지 않음의 기본값을 의미 함)

당신이 경우 __eq__사용하지 않는 NotImplemented반환, (의미 오버 헤드)이 작품은 사용 않는 경우 NotImplemented제대로 때때로,이 핸들을. 그리고 Python 버전 검사는 클래스가 importPython 3에서 -ed 인 경우 __ne__정의되지 않은 상태로 유지되어 Python의 기본적이고 효율적인 폴백 __ne__구현 (위의 C 버전) 이 대신 할 수 있음을 의미합니다.


이것이 필요한 이유

Python 오버로딩 규칙

다른 솔루션 대신이 작업을 수행하는 이유에 대한 설명은 다소 난해합니다. Python에는 연산자 오버로딩과 특히 비교 연산자에 대한 몇 가지 일반적인 규칙이 있습니다.

  1. 실행하는 경우 (모든 사업자에 적용) LHS OP RHS, 시도LHS.__op__(RHS) ,이 값이 반환 NotImplemented되면 try RHS.__rop__(LHS). 예외 : RHS이 클래스의 하위 클래스 인 경우 먼저LHS 테스트하십시오 . 비교 연산자의 경우, 과 에 대한 테스트 순서가 있으므로 자신의 'ROP'의이다 ( 이다 , 다음 경우, 반전 의 서브 클래스RHS.__rop__(LHS) __eq____ne____ne__LHS.__ne__(RHS)RHS.__ne__(LHS)RHSLHS 의 클래스)
  2. "교환 된"연산자의 개념을 제외하고 연산자간에 암시적인 관계가 없습니다. 동일한 클래스의 경우에도 LHS.__eq__(RHS)반환 TrueLHS.__ne__(RHS)반환을 의미하지 않습니다 False(사실 연산자는 부울 값을 반환 할 필요도 없습니다. SQLAlchemy와 같은 ORM은 의도적으로 그렇지 않아보다 표현적인 쿼리 구문을 허용합니다). Python 3부터 기본 __ne__구현은 이러한 방식으로 작동하지만 계약이 아닙니다. __ne__.NET의 엄격한 반대가 아닌 방식으로 재정의 할 수 있습니다 __eq__.

이것이 오버로딩 비교기에 적용되는 방법

따라서 연산자를 오버로드하면 두 가지 작업이 있습니다.

  1. 사용자가 작업을 직접 구현하는 방법을 알고 있다면, 사용, 그렇게 있는 경우 비교를 수행하는 방법에 대한 자신의 지식 수행하십시오 (암시 적 또는 명시 적으로 작업의 다른쪽에 위임하지 마십시오. 그렇게하면 부정확성 및 / 또는 무한 재귀 위험이 있습니다. 당신이 그것을하는 방법에 따라)
  2. 당신이 경우 하지 않는 동작 자신을 구현하는 방법을 알고, 항상 반환 NotImplemented파이썬은 다른 피연산자의 구현에 위임 할 수 있도록,

문제 not self.__eq__(other)

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

절대 다른쪽에 위임하지 않습니다 ( __eq__올바르게 반환하는 경우 올바르지 않음 NotImplemented). 때 self.__eq__(other)반환 NotImplemented( "truthy"입니다), 당신은 자동으로 반환 False, 그래서 A() != something_A_knows_nothing_about반환 False, 그것은 확인해야 할 때 경우 something_A_knows_nothing_about의 인스턴스를 비교하는 방법을 알고 있었다A , True그렇지 않은 경우 반환 했어야 합니다. 다른 것과 비교하면 서로 같지 않은 것으로 간주됩니다). A.__eq__이 잘못 구현 된 경우 ( 다른 쪽을 인식하지 못할 때 False대신 반환 됨 NotImplemented) 이것은 A의 관점 에서 "올바른"것입니다 반환합니다True ( A동등하다고 생각하지 않기 때문에 동일하지 않음). ~에서 잘못something_A_knows_nothing_about의 관점은 결코 묻지도 않았기 때문에 something_A_knows_nothing_about; A() != something_A_knows_nothing_about최대 종료 할 수True 하지만,something_A_knows_nothing_about != A()False또는 기타 반환 값.

문제 not self == other

def __ne__(self, other):
    return not self == other

더 미묘합니다. __ne__의 논리적 역인 모든 클래스를 포함하여 클래스의 99 %에 대해 정확할 것 입니다 __eq__. 그러나 not self == other클래스의 수단 규칙 모두 위에서 언급 한 휴식, __ne__ 아니다 의 논리 역 __eq__이 구현할 수있는 경우 피연산자 중 하나가 요구하지 않기 때문에, 결과는 다시 한번 비 대칭 __ne__모든에서도 다른 경우, 피연산자는 할 수 없습니다. 가장 간단한 예는 이상한 클래스 반환 False에 대한 모든 비교는, 이렇게 A() == Incomparable()하고 A() != Incomparable()모두 반환 False. A.__ne__( NotImplemented비교를 수행하는 방법을 모를 때 반환되는) 의 올바른 구현으로 , 관계는 대칭 적입니다. A() != Incomparable()Incomparable() != A()결과에 동의합니다 (전자의 경우를 A.__ne__반환 NotImplementedIncomparable.__ne__다음를 반환 False하고 후자의 경우 직접 Incomparable.__ne__반환하기 때문입니다 False). 그러나 경우 A.__ne__로 구현됩니다 return not self == other, A() != Incomparable()반환 True(때문에 A.__eq__반환하지 NotImplementedIncomparable.__eq__반환 False하고, A.__ne__반전 것과 True) 동안 Incomparable() != A()반환False.

여기 에서이 작업의 예를 볼 수 있습니다 .

물론, 항상 반환하는 클래스 False모두 __eq____ne__조금 이상하다. 그러나 앞에서 언급 한로 __eq____ne__도 반환 할 필요가 없습니다 True/False ; SQLAlchemy의 ORM은 비교기와 클래스가 그 반환 쿼리 작성을위한 특별한 프록시 객체가 아닌 True/ False모든 (그들이있는 거 "truthy"경우 부울 맥락에서 평가하지만,이 같은 맥락에서 평가 안되는)에서.

과부하에 실패하여 __ne__제대로하면 됩니다 코드로, 그런 종류의 클래스를 휴식 :

 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())

작동합니다 (SQLAlchemy가 MyClassWithBadNESQL 문자열에 삽입하는 방법을 전혀 알고 있다고 가정 합니다.이 작업 MyClassWithBadNE은 전혀 협력 할 필요 없이 유형 어댑터로 수행 할 수 있음 ). 예상되는 프록시 객체를에 전달하는 filter동안

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)

전달 끝날 것 filter일반을 False하기 때문에, self == other반환 프록시 객체, 그리고 not self == other단지에 truthy 프록시 개체를 변환합니다 False. 바라 건데, filter같이 유효하지 않은 인수를 처리되고에서 예외가 발생합니다 False. 나는 많은 사람들이 비교의 왼쪽에 일관되게 MyTable.fieldname 있어야 한다고 주장 할 것이라고 확신하지만 , 사실은 일반적인 경우에 이것을 시행 할 프로그래밍적인 이유가 없으며 올바른 제네릭 __ne__은 어느 쪽이든 return not self == other작동 하지만 작동하는 것입니다. 하나의 배열로.


1
유일하게 정확하고 완전하며 정직한 (죄송합니다 @AaronHall) 답변. 이것은 받아 들여진 대답이어야합니다.
마계로

4

짧은 대답 : 예 (하지만 올바르게 수행하려면 설명서를 읽으십시오)

ShadowRanger의 __ne__메소드 구현은 올바른 것입니다 ( __ne__파이썬 3.4 이후 메소드 의 기본 구현입니다 ).

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

왜? 중요한 수학적 속성 인 연산자 의 대칭 을 유지하기 때문 !=입니다. 이 연산자는 바이너리 이므로 그 결과는 하나가 아닌 피연산자 의 동적 유형에 따라 달라집니다 . 이것은 다중 디스패치를 허용 하는 프로그래밍 언어에 대한 이중 디스패치 를 통해 구현됩니다. (예 : Julia )에 . 단일 디스패치 만 허용하는 Python에서는 값을 반환하여 숫자 메서드풍부한 비교 메서드 에 대해 이중 디스패치를 ​​시뮬레이션 합니다.NotImplemented 다른 피연산자의 유형을 지원하지 않는 구현 메소드 . 그러면 인터프리터는 다른 피연산자의 반영된 방법을 시도합니다.

Aaron Hall의 방법 구현 not self == other은 운영자 __ne__의 대칭을 제거하기 때문에 올바르지 않습니다 !=. 사실, 결코 돌아올 수 없습니다NotImplemented ( not NotImplementedis False)를__ne__ 우선 순위가 높은 __ne__메서드는 우선 순위가 낮은 메서드로 폴백 할 수 없습니다 . not self == other이전에는 __ne__메서드 의 기본 Python 3 구현 이었지만 ShadowRanger가 발견 한대로 2015 년 1 월 Python 3.4에서 수정 된 버그였습니다 ( 문제 # 21408 참조) . ).

비교 연산자의 구현

파이썬 언어 참조 파이썬에 대한 3 주 장 III 데이터 모델 :

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

이것이 소위“풍부한 비교”방법입니다. 연산자 기호와 메소드 이름 간의 대응은 다음과 같습니다. x<ycalls x.__lt__(y), x<=ycalls x.__le__(y),x==y calls x.__eq__(y), x!=ycalls x.__ne__(y), x>ycalls x.__gt__(y)x>=y calls x.__ge__(y).

풍부한 비교 메서드는 NotImplemented주어진 인수 쌍에 대한 연산을 구현하지 않으면 싱글 톤을 반환 할 수 있습니다 .

이러한 메서드의 인수가 교체 된 버전은 없습니다 (왼쪽 인수가 작업을 지원하지 않지만 오른쪽 인수가 지원할 때 사용됨). 오히려, __lt__()__gt__() 서로의 반영이다, __le__()그리고 __ge__()서로의 반사하고, __eq__()그리고 __ne__()자신의 반영이다. 피연산자가 다른 유형이고 오른쪽 피연산자의 유형이 왼쪽 피연산자 유형의 직접 또는 간접 서브 클래스 인 경우 오른쪽 피연산자의 반영된 메소드가 우선 순위를 가지며 그렇지 않으면 왼쪽 피연산자의 메소드가 우선 순위를 갖습니다. 가상 서브 클래 싱은 고려되지 않습니다.

이것을 파이썬 코드로 번역하면 ( operator_eqfor ==, operator_nefor !=, operator_ltfor <, operator_gtfor >, operator_lefor<=operator_gefor 사용 >=).

def operator_eq(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__eq__(left)

        if result is NotImplemented:
            result = left.__eq__(right)
    else:
        result = left.__eq__(right)

        if result is NotImplemented:
            result = right.__eq__(left)

    if result is NotImplemented:
        result = left is right

    return result


def operator_ne(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ne__(left)

        if result is NotImplemented:
            result = left.__ne__(right)
    else:
        result = left.__ne__(right)

        if result is NotImplemented:
            result = right.__ne__(left)

    if result is NotImplemented:
        result = left is not right

    return result


def operator_lt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__gt__(left)

        if result is NotImplemented:
            result = left.__lt__(right)
    else:
        result = left.__lt__(right)

        if result is NotImplemented:
            result = right.__gt__(left)

    if result is NotImplemented:
        raise TypeError(f"'<' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_gt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__lt__(left)

        if result is NotImplemented:
            result = left.__gt__(right)
    else:
        result = left.__gt__(right)

        if result is NotImplemented:
            result = right.__lt__(left)

    if result is NotImplemented:
        raise TypeError(f"'>' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_le(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ge__(left)

        if result is NotImplemented:
            result = left.__le__(right)
    else:
        result = left.__le__(right)

        if result is NotImplemented:
            result = right.__ge__(left)

    if result is NotImplemented:
        raise TypeError(f"'<=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result


def operator_ge(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__le__(left)

        if result is NotImplemented:
            result = left.__ge__(right)
    else:
        result = left.__ge__(right)

        if result is NotImplemented:
            result = right.__le__(left)

    if result is NotImplemented:
        raise TypeError(f"'>=' not supported between instances of '{type(left).__name__}' and '{type(right).__name__}'")

    return result

비교 방법의 기본 구현

문서는 다음을 추가합니다.

기본적 으로는를 __ne__()위임 __eq__()하고 결과가 아닌 경우 결과를 반전합니다 NotImplemented. 비교 연산자 간에는 다른 암시 적 관계 (x<y or x==y)가 없습니다 x<=y. 예를 들어의 진실은 암시하지 않습니다 .

비교 방법의 기본 구현 ( __eq__, __ne__, __lt__, __gt__, __le__및은 __ge__) 이에 의해 주어질 수있다 :

def __eq__(self, other):
    return NotImplemented

def __ne__(self, other):
    result = self.__eq__(other)

    if result is not NotImplemented:
        return not result

    return NotImplemented

def __lt__(self, other):
    return NotImplemented

def __gt__(self, other):
    return NotImplemented

def __le__(self, other):
    return NotImplemented

def __ge__(self, other):
    return NotImplemented

그래서 이것은 __ne__방법 의 올바른 구현입니다 . 그리고 메서드가를 반환 __eq__할 때 역 이 (as) 이기 때문에 항상 메서드 의 역을 반환하지는 않습니다.__eq__NotImplementednot NotImplementedFalsebool(NotImplemented) 것입니다 True) 대신 원하는 중 NotImplemented.

잘못된 구현 __ne__

Aaron Hall이 위에서 설명한 것처럼 메서드 not self.__eq__(other)의 기본 구현은 아닙니다 __ne__. 그러나도있다 not self == other. 후자는 not self == other두 가지 경우에 기본 구현의 동작과 구현의 동작을 비교하여 아래에 설명되어 있습니다.

  • __eq__메소드가 리턴 NotImplemented;
  • __eq__메서드는와 다른 값을 반환합니다 NotImplemented.

기본 구현

A.__ne__메서드가 기본 구현을 사용하고 A.__eq__메서드가 NotImplemented다음을 반환 할 때 어떤 일이 발생하는지 살펴 보겠습니다 .

class A:
    pass


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) == "B.__ne__"
  1. !=전화 A.__ne__.
  2. A.__ne__전화 A.__eq__.
  3. A.__eq__를 반환합니다 NotImplemented.
  4. !=전화 B.__ne__.
  5. B.__ne__를 반환합니다 "B.__ne__".

때이 쇼 A.__eq__방법 반환 NotImplementedA.__ne__방법은 다시 떨어진다B.__ne__ 방법.

이제 A.__ne__메서드가 기본 구현을 사용하고 메서드가 다음과 A.__eq__다른 값을 반환 할 때 어떤 일이 발생하는지 살펴 보겠습니다 NotImplemented.

class A:

    def __eq__(self, other):
        return True


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=전화 A.__ne__.
  2. A.__ne__전화 A.__eq__.
  3. A.__eq__를 반환합니다 True.
  4. !=반환 not True, 즉 False.

이것은이 경우 A.__ne__메서드가 메서드의 역을 반환 함을 보여줍니다 A.__eq__. 그래서__ne__ 메서드는 문서에 광고 된 것처럼 작동합니다.

의 기본 구현 재정의 A.__ne__위에 제공된 올바른 구현으로 메서드 동일한 결과가 생성됩니다.

not self == other 이행

A.__ne__메소드 의 기본 구현을 구현으로 재정의 not self == other하고 A.__eq__메소드가 NotImplemented다음을 반환 할 때 어떤 일이 발생하는지 살펴 보겠습니다 .

class A:

    def __ne__(self, other):
        return not self == other


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is True
  1. !=전화 A.__ne__.
  2. A.__ne__전화 ==.
  3. ==전화 A.__eq__.
  4. A.__eq__를 반환합니다 NotImplemented.
  5. ==전화 B.__eq__.
  6. B.__eq__를 반환합니다 NotImplemented.
  7. ==반환 A() is B(), 즉 False.
  8. A.__ne__반환 not False, 즉 True.

__ne__반환 된 메서드 의 기본 구현은 "B.__ne__"아닙니다 True.

이제 구현으로 A.__ne__메서드 의 기본 구현을 재정의하고 메서드가 다음 not self == otherA.__eq__다른 값을 반환 할 때 어떤 일이 발생하는지 살펴 보겠습니다 NotImplemented.

class A:

    def __eq__(self, other):
        return True

    def __ne__(self, other):
        return not self == other


class B:

    def __ne__(self, other):
        return "B.__ne__"


assert (A() != B()) is False
  1. !=전화 A.__ne__.
  2. A.__ne__전화 ==.
  3. ==전화 A.__eq__.
  4. A.__eq__를 반환합니다 True.
  5. A.__ne__반환 not True, 즉 False.

__ne__메서드 의 기본 구현 도 반환되었습니다.False이 경우 됩니다.

이 구현 __ne____eq__메서드가를 반환 할 때 메서드 의 기본 구현 동작을 복제하지 못하므로 NotImplemented잘못된 것입니다.


마지막 예 : "이 구현 __ne____eq__메서드가 NotImplemented를 반환 할 때 메서드 의 기본 구현 동작을 복제하지 못하므로 잘못된 것입니다." - A무조건 평등을 정의합니다. 따라서 A() == B(). 따라서 A() != B() 거짓이어야한다 , 그것은 이다 . 주어진 예는 병리 적입니다 (즉 __ne__, 문자열을 반환 __eq__해서는 안되며, 의존해서는 안됩니다. __ne__대신 Python 3의 기본 기대치 인에 __ne__의존해야합니다 __eq__). 내 마음을 바꿀 수있을 때까지 나는 여전히이 대답에 -1입니다.
Aaron Hall

@AaronHall Python 언어 참조에서 : "풍부한 비교 방법은 싱글을 반환 할 수 있습니다 NotImplemented.이 인수의 주어진 쌍에 대한 작업을 구현하고 있지 않는 경우 관례 적으로, False그리고 True성공적인 비교를 위해 반환됩니다. 그러나, 이러한 방법은 어떤 값을 반환 할 수 있습니다 따라서 비교 연산자가 부울 컨텍스트 (예 : if 문의 조건에서)에서 사용되는 경우 Python은 bool()값을 호출 하여 결과가 참인지 거짓인지를 결정합니다. "
마계로

@AaronHall 당신의 구현은 __ne__중요한 수학적 속성 인 대칭 을 죽입니다.!= 연산자 인 입니다. 이 연산자는 이진이므로 그 결과는 하나뿐 아니라 피연산자 의 동적 유형에 따라 달라집니다 . 이것은 다중 디스패치를 허용 하는 언어에 대한 이중 디스패치 를 통해 프로그래밍 언어에서 올바르게 구현됩니다 . 단일 디스패치를 ​​허용하는 Python에서는 값 을 반환하여 이중 디스패치를 ​​시뮬레이션 합니다. NotImplemented
마계로

마지막 예는 두 개의 클래스가, B그 반환에 대한 모든 검사에 truthy 문자열 __ne__, 그리고 A그 반환 True에 대한 모든 검사에 __eq__. 이것은 병리학 적 모순입니다. 이러한 모순에서 예외를 제기하는 것이 가장 좋습니다. 지식없이 B, A존중 의무가 B의 구현 __ne__대칭의 목적을. 예제의 그 시점에서 A구현 __ne__은 나에게 무관합니다. 당신의 요점을 만들기 위해 실용적이고 비 병리적인 사례를 찾으십시오. 나는 당신을 해결하기 위해 내 답변을 업데이트했습니다.
Aaron Hall

@AaronHall보다 현실적인 예는 @ShadowRanger에서 제공하는 SQLAlchemy 예를 참조하십시오. 또한 __ne__일반적인 사용 사례에서 작업을 구현 하는 것이 올바른 방법이 아닙니다. Boeing 737 MAX 항공기는 추락 전에 50 만 대를 비행했습니다…
Maggyero

-1

모두의 경우 __eq__, __ne__, __lt__, __ge__, __le__, 및 __gt__클래스의 메이크업 감각, 그럼 그냥 구현 __cmp__대신. 그렇지 않으면 Daniel DiPaolo가 말한 비트 때문에 (내가 그것을 찾는 대신 테스트하는 동안;))


12
__cmp__()특별한 방법은 더 이상 부자 비교 연산자를 사용하여 익숙해한다고, 그래서 파이썬 3.x를 지원하지 않습니다.
Don O'Donnell

8
또는 Python 2.7 또는 3.x를 사용하는 경우 functools.total_ordering 데코레이터도 매우 편리합니다.
Adam Parkin 2012

알려 주셔서 감사합니다. 그래도 지난 1 년 반 동안 그 라인을 따라 많은 것을 깨닫게되었습니다. ;)
Karl Knechtel 2012
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.