질문 : 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 == n3
및n3 == n1
calln3.__eq__
;n1 != n3
및n3 != 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
'프로그래밍 언어 > Python' 카테고리의 다른 글
Python에서 수동으로 예외 발생 (throwing) (0) | 2021.08.12 |
---|---|
pip로 특정 패키지 버전 설치 (0) | 2021.08.12 |
Python unittest-assert Raises의 반대? (0) | 2021.08.11 |
마이크로 초 구성 요소없이 문자열에 대한 Python 날짜 시간 (0) | 2021.08.11 |
Python에서 None 판별법 (0) | 2021.08.10 |