programing

Python 클래스의 동등성("equality")을 지원하는 우아한 방법

procenter 2022. 9. 24. 22:18
반응형

Python 클래스의 동등성("equality")을 지원하는 우아한 방법

해 주는 합니다.== ★★★★★★★★★★★★★★★★★」!=오퍼레이터.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?s?

주의: 설명하겠습니다.__eq__ ★★★★★★★★★★★★★★★★★」__ne__정의되어 있지 않습니다.이 동작은 다음과 같습니다.

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

그것은,a == bFalse 운행되기 때문이다.a is b "", "Is", "Is")a.와 같은 오브젝트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이치노에는 Python 3으로 .class A:,class A(object): ★★★★★★★★★★★★★★★★★」class A(B):

classic-style 클래스의 경우 비교연산은 항상 첫 번째 피연산자의 메서드를 호출하지만 new-style 클래스의 경우 피연산자의 순서에 관계없이 항상 서브클래스 피연산자의 메서드를 호출합니다.

'만나면'이, '만나면'이Number클래스입니다.

  • n1 == n3 ®n1.__eq__;
  • n3 == n1 ®n3.__eq__;
  • n1 != n3 ®n1.__ne__;
  • n3 != n1 ®n3.__ne__.

만약 ★★★★★★★★★★★★★★★★★.Number는 새로운 클래스입니다.

  • n1 == n3 ★★★★★★★★★★★★★★★★★」n3 == n1n3.__eq__;
  • n1 != n3 ★★★★★★★★★★★★★★★★★」n3 != n1n3.__ne__.

「 」의 하려면 , 「 」== ★★★★★★★★★★★★★★★★★」!= 2 , Python 2 classic-style 클래스 연산자,__eq__ ★★★★★★★★★★★★★★★★★」__ne__는 ""를 .NotImplemented오퍼랜드 타입이 값은 오퍼랜드유형이 지원되지 않습니다.매뉴얼에 정의되어 있습니다.NotImplemented 추가:

수치 메서드 및 리치 비교 메서드는 제공된 오퍼랜드에 대한 연산을 구현하지 않을 경우 이 값을 반환할 수 있습니다.(그 후 인터프리터는 오퍼레이터에 따라 반사된 조작 또는 기타 폴백을 시도합니다).그것의 진실한 가치는 진실이다.

이 경우 연산자는 비교 연산을 다른 피연산자의 반영 방법에 위임합니다. 문서에서는 반영 방법을 다음과 같이 정의하고 있습니다.

인수가 대신 swap-argument는 swap-argument(swap-argument)입니다.__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은 개체의 ID 해시를 반환합니다.덮어쓰기를 시도합니다.

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_total(cls)

하나 이상의 리치 비교 순서 지정 메서드를 정의하는 클래스가 지정되면 이 클래스 데코레이터가 나머지를 제공합니다.이렇게 하면 가능한 모든 리치 비교 작업을 지정하는 작업이 단순해집니다.

는 래음음음음음음음음음음음 of of of of 중 .__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()))

다 는 없어요.__eq__ ★★★★★★★★★★★★★★★★★」__ne__ 수 것은 「」뿐입니다.__cmp__그러나 이는 ==, !==, < , > 등의 결과에 영향을 미칩니다.

is아이디은 ,을 합니다.is는 b가 됩니다.Truea와 b가 모두 동일한 객체에 대한 참조를 보유하고 있는 경우.python에서는 항상 실제 객체가 아닌 변수 내의 객체에 대한 참조를 유지하므로 기본적으로 a의 경우 b가 true가 되려면 해당 객체의 객체는 동일한 메모리 위치에 있어야 합니다.어떻게 그리고 가장 중요한 것은 왜 이 행동을 무시하려고 하는가?

__cmp__.

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

is' 테스트는 기본적으로 개체의 메모리 주소를 반환하므로 오버로드할 수 없는 내장된 id() 함수를 사용하여 ID를 테스트합니다.

그러나 클래스의 동등성을 테스트하는 경우에는 테스트에 대해 조금 더 엄격히 하고 클래스의 데이터 속성만 비교해야 합니다.

import types

class ComparesNicely(object):

    def __eq__(self, other):
        for key, value in self.__dict__.iteritems():
            if (isinstance(value, types.FunctionType) or 
                    key.startswith("__")):
                continue

            if key not in other.__dict__:
                return False

            if other.__dict__[key] != value:
                return False

         return True

이 코드는 클래스의 비함수 데이터 멤버만 비교하고 일반적으로 원하는 프라이빗 데이터 멤버는 건너뜁니다.Plain Old Python Objects의 경우 __init__, __str__, __repr_ 및 __eq__를 구현하는 기본 클래스가 있으므로 POPO 객체는 추가 로직(대부분 동일한 경우)의 부담을 지지 않습니다.

서브클래스/믹신 대신 범용 클래스 데코레이터를 사용하고 싶다.

def comparable(cls):
    """ Class decorator providing generic comparison functionality """

    def __eq__(self, other):
        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__

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

    cls.__eq__ = __eq__
    cls.__ne__ = __ne__
    return cls

사용방법:

@comparable
class Number(object):
    def __init__(self, x):
        self.x = x

a = Number(1)
b = Number(1)
assert a == b

이것은 Algorias의 답변에 대한 코멘트를 포함하며, 전체 dict에 대해 신경 쓰지 않기 때문에 하나의 속성으로 객체를 비교합니다. hasattr(other, "id")사실일 거예요. 하지만 제가 컨스트럭터에 설정했기 때문에 그런 거 알아요.

def __eq__(self, other):
    if other is self:
        return True

    if type(other) is not type(self):
        # delegate to superclass
        return NotImplemented

    return other.id == self.id

으로 커스텀 했습니다.__ne__ __eq__:

class HasEq(object):
  """
  Mixin that provides a default implementation of ``object.__neq__`` using the subclass's implementation of ``object.__eq__``.

  This overcomes Python's deficiency of ``==`` and ``!=`` not being symmetric when overloading comparison operators
  (i.e. ``not x == y`` *does not* imply that ``x != y``), so whenever you implement
  `object.__eq__ <https://docs.python.org/2/reference/datamodel.html#object.__eq__>`_, it is expected that you
  also implement `object.__ne__ <https://docs.python.org/2/reference/datamodel.html#object.__ne__>`_

  NOTE: in Python 3+ this is no longer necessary (see https://docs.python.org/3/reference/datamodel.html#object.__ne__)
  """

  def __ne__(self, other):
    """
    Default implementation of ``object.__ne__(self, other)``, delegating to ``self.__eq__(self, other)``.

    When overriding ``object.__eq__`` in Python, one should also override ``object.__ne__`` to ensure that
    ``not x == y`` is the same as ``x != y``
    (see `object.__eq__ <https://docs.python.org/2/reference/datamodel.html#object.__eq__>`_ spec)

    :return: ``NotImplemented`` if ``self.__eq__(other)`` returns ``NotImplemented``, otherwise ``not self.__eq__(other)``
    """
    equal = self.__eq__(other)
    # the above result could be either True, False, or NotImplemented
    if equal is NotImplemented:
      return NotImplemented
    return not equal

클래스에서 구현만 .__eq__그리고 베이스.

돌이켜보면, 더 나은 접근법은 그것을 대신 데코레이터로 구현하는 것이었을지도 모른다.뭐랄까

언급URL : https://stackoverflow.com/questions/390250/elegant-ways-to-support-equivalence-equality-in-python-classes

반응형