__eq__()
연산자 를 재정의하려는 클래스가 있습니다 . __ne__()
연산자도 재정의해야한다는 것이 이해가되는 것 같지만 , __ne__
기반으로 구현하는 것이 합리적입니까?__eq__
입니까?
class A:
def __eq__(self, other):
return self.value == other.value
def __ne__(self, other):
return not self.__eq__(other)
아니면 파이썬이 이러한 연산자를 사용하는 방식에서 빠진 것이 있는데 이것이 좋은 생각이 아닙니다.
답변
예, 완벽합니다. 실제로 문서 에서는 다음을 정의 __ne__
할 때 정의하도록 촉구합니다 __eq__
.
비교 연산자 간에는 암시 적 관계가 없습니다. 의 진실은
x==y
그것이x!=y
거짓 임을 의미하지 않습니다 . 따라서 정의 할 때
연산자가 예상대로 작동하도록__eq__()
정의__ne__()
해야합니다.
많은 경우 (예 :이 경우)의 결과를 부정하는 것처럼 간단 __eq__
하지만 항상 그런 것은 아닙니다.
답변
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__
절대 반환 할 수 없기 때문에 올바르지 않으므로 우선 순위가 있는 메서드는 우선 순위가없는 메서드로 대체 될 수 없습니다 .NotImplemented
not NotImplemented
False
__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에서 저수준 논리를 작성 하지 마십시오 .
답변
기록을 위해 정규적으로 정확하고 교차하는 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 버전 검사는 클래스가 import
Python 3에서 -ed 인 경우 __ne__
정의되지 않은 상태로 유지되어 Python의 기본적이고 효율적인 폴백 __ne__
구현 (위의 C 버전) 이 대신 할 수 있음을 의미합니다.
이것이 필요한 이유
Python 오버로딩 규칙
다른 솔루션 대신이 작업을 수행하는 이유에 대한 설명은 다소 난해합니다. Python에는 연산자 오버로딩과 특히 비교 연산자에 대한 몇 가지 일반적인 규칙이 있습니다.
- 실행하는 경우 (모든 사업자에 적용)
LHS OP RHS
, 시도LHS.__op__(RHS)
,이 값이 반환NotImplemented
되면 tryRHS.__rop__(LHS)
. 예외 :RHS
이 클래스의 하위 클래스 인 경우 먼저LHS
테스트하십시오 . 비교 연산자의 경우, 과 에 대한 테스트 순서가 있으므로 자신의 ‘ROP’의이다 ( 이다 , 다음 경우, 반전 의 서브 클래스RHS.__rop__(LHS)
__eq__
__ne__
__ne__
LHS.__ne__(RHS)
RHS.__ne__(LHS)
RHS
LHS
의 클래스) - “교환 된”연산자의 개념을 제외하고 연산자간에 암시적인 관계가 없습니다. 동일한 클래스의 경우에도
LHS.__eq__(RHS)
반환True
은LHS.__ne__(RHS)
반환을 의미하지 않습니다False
(사실 연산자는 부울 값을 반환 할 필요도 없습니다. SQLAlchemy와 같은 ORM은 의도적으로 그렇지 않아보다 표현적인 쿼리 구문을 허용합니다). Python 3부터 기본__ne__
구현은 이러한 방식으로 작동하지만 계약이 아닙니다.__ne__
.NET의 엄격한 반대가 아닌 방식으로 재정의 할 수 있습니다__eq__
.
이것이 오버로딩 비교기에 적용되는 방법
따라서 연산자를 오버로드하면 두 가지 작업이 있습니다.
- 사용자가 작업을 직접 구현하는 방법을 알고 있다면, 사용, 그렇게 만 있는 경우 비교를 수행하는 방법에 대한 자신의 지식 수행하십시오 (암시 적 또는 명시 적으로 작업의 다른쪽에 위임하지 마십시오. 그렇게하면 부정확성 및 / 또는 무한 재귀 위험이 있습니다. 당신이 그것을하는 방법에 따라)
- 당신이 경우 하지 않는 동작 자신을 구현하는 방법을 알고, 항상 반환
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__
반환 NotImplemented
한 Incomparable.__ne__
다음를 반환 False
하고 후자의 경우 직접 Incomparable.__ne__
반환하기 때문입니다 False
). 그러나 경우 A.__ne__
로 구현됩니다 return not self == other
, A() != Incomparable()
반환 True
(때문에 A.__eq__
반환하지 NotImplemented
후 Incomparable.__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가 MyClassWithBadNE
SQL 문자열에 삽입하는 방법을 전혀 알고 있다고 가정 합니다.이 작업 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
작동 하지만 작동하는 것입니다. 하나의 배열로.
답변
짧은 대답 : 예 (하지만 올바르게 수행하려면 설명서를 읽으십시오)
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 NotImplemented
is 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<y
calls
x.__lt__(y)
,x<=y
callsx.__le__(y)
,x==y
callsx.__eq__(y)
,
x!=y
callsx.__ne__(y)
,x>y
callsx.__gt__(y)
및x>=y
callsx.__ge__(y)
.풍부한 비교 메서드는
NotImplemented
주어진 인수 쌍에 대한 연산을 구현하지 않으면 싱글 톤을 반환 할 수 있습니다 .이러한 메서드의 인수가 교체 된 버전은 없습니다 (왼쪽 인수가 작업을 지원하지 않지만 오른쪽 인수가 지원할 때 사용됨). 오히려,
__lt__()
및__gt__()
서로의 반영이다,__le__()
그리고__ge__()
서로의 반사하고,
__eq__()
그리고__ne__()
자신의 반영이다. 피연산자가 다른 유형이고 오른쪽 피연산자의 유형이 왼쪽 피연산자 유형의 직접 또는 간접 서브 클래스 인 경우 오른쪽 피연산자의 반영된 메소드가 우선 순위를 가지며 그렇지 않으면 왼쪽 피연산자의 메소드가 우선 순위를 갖습니다. 가상 서브 클래 싱은 고려되지 않습니다.
이것을 파이썬 코드로 번역하면 ( operator_eq
for ==
, operator_ne
for !=
, operator_lt
for <
, operator_gt
for >
, operator_le
for<=
및 operator_ge
for 사용 >=
).
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__
NotImplemented
not NotImplemented
False
bool(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__"
!=
전화A.__ne__
.A.__ne__
전화A.__eq__
.A.__eq__
를 반환합니다NotImplemented
.!=
전화B.__ne__
.B.__ne__
를 반환합니다"B.__ne__"
.
때이 쇼 A.__eq__
방법 반환 NotImplemented
의 A.__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
!=
전화A.__ne__
.A.__ne__
전화A.__eq__
.A.__eq__
를 반환합니다True
.!=
반환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
!=
전화A.__ne__
.A.__ne__
전화==
.==
전화A.__eq__
.A.__eq__
를 반환합니다NotImplemented
.==
전화B.__eq__
.B.__eq__
를 반환합니다NotImplemented
.==
반환A() is B()
, 즉False
.A.__ne__
반환not False
, 즉True
.
__ne__
반환 된 메서드 의 기본 구현은 "B.__ne__"
아닙니다 True
.
이제 구현으로 A.__ne__
메서드 의 기본 구현을 재정의하고 메서드가 다음 not self == other
과 A.__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
!=
전화A.__ne__
.A.__ne__
전화==
.==
전화A.__eq__
.A.__eq__
를 반환합니다True
.A.__ne__
반환not True
, 즉False
.
__ne__
메서드 의 기본 구현 도 반환되었습니다.False
이 경우 됩니다.
이 구현 __ne__
은 __eq__
메서드가를 반환 할 때 메서드 의 기본 구현 동작을 복제하지 못하므로 NotImplemented
잘못된 것입니다.
답변
모두의 경우 __eq__
, __ne__
, __lt__
, __ge__
, __le__
, 및 __gt__
클래스의 메이크업 감각, 그럼 그냥 구현 __cmp__
대신. 그렇지 않으면 Daniel DiPaolo가 말한 비트 때문에 (내가 그것을 찾는 대신 테스트하는 동안;))