Supports elegant methods of equivalence ("equality") in Python classes

When writing custom classes, it is often important to allow equivalence through the==and!=operators.In Python, this can be achieved by implementing the u eq_ and u ne_ special methods, respectively.I find the easiest way to do this is to do the following:

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)

Do you know a more elegant way to do this?Do you know any particular drawbacks of using the above method of comparing u dict_u s?

Note: One thing to clarify is that when u eq_ and u ne_ are undefined, you will find the following behavior:

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

That is, a == b evaluates to False because it does run a is b, an identity test (i.e.'Is a same object b?').

Define u eq_u and u ne_u, and you will find this behavior (this is what we want u ne_u behavior):

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

#1st floor

This is not a direct answer, but seems relevant enough to solve it, as it sometimes saves some tedious work.Cut out directly from the document...

functools.total_ordering(cls)

Given a class that defines one or more rich comparison ordering methods, such adorners will provide the remaining classes.This simplifies the work involved in specifying all possible enrichment comparisons:

This class must define u lt_(), u le_()u lt_(), u le_(), u le_(), u gt_(), or u ge_().In addition, the class should provide the u eq_() method.

New features for Version 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()))

#2nd floor

Consider this simple question:

class Number:

    def __init__(self, number):
        self.number = number


n1 = Number(1)
n2 = Number(1)

n1 == n2 # False -- oops

Therefore, by default, Python uses object identifiers for comparison operations:

id(n1) # 140400634555856
id(n2) # 140400634555920

Rewriting the u eq_u function seems to solve this problem:

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

In Python 2, always remember to also override u ne_u functions, such as File Described:

There is no implicit relationship between comparison operators.The truth of x==y does not mean x!=y is false.Therefore, in defining u eq_(), you should also define u ne_() to make the operator behave as expected.

def __ne__(self, other):
    """Overrides the default implementation (unnecessary in Python 3)"""
    return not self.__eq__(other)


n1 == n2 # True
n1 != n2 # False

In Python 3, this is no longer necessary because File Point out:

By default, u ne_() is delegated to u eq_() and the results are reversed unless it is NotImplemented.There are no other implicit relationships between comparison operators, such as (x<y or x==y) the truth does not mean x<=y.

But that doesn't solve all our problems.Let's add a subclass:

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

Note: There are two types of Python 2:

  • Classic Style (or old style) classes, which do not inherit from an object and are declared as class A: class A(): or class A(B): where B is a classic style class;

  • New type Classes that inherit objects and are declared class A(object) or class A(B): where B is a new type of class.Python 3 only has new classes declared as class A: class A(object): class A(B): or class A(B): classes.

For classic-style classes, the comparison always calls the method of the first operand, while for new-style classes, the method of the subclass operand is always called. Without regard to Operand Order of .

So here, if Number is a Classic Style class:

  • n1 == n3 calls n1. u eq_u;
  • n3 == n1 calls n3. u eq_u;
  • N1!= N3 calls n1. u ne_;
  • N3!= N1 calls n3. u ne_u.

And if Number is a new class:

  • n1 == n3 and n3 == n1 calls n3. u eq_u;
  • N1!= N3 and n3!= N1 calls n3. u ne_u.

The non-interchangeability problem to fix== and!= is a Python 2 classic style class, and the operator_u eq_u and u ne_u methods should return a NotImplemented value for an unsupported type of operation.this File Define the NotImplemented value as:

This value may be returned if the number method and the enrichment comparison method do not implement the operation of the provided operand.(The interpreter will then attempt to perform a reflection operation or other fallback based on the operator.)Its true value is true.

In this case, the operator delegates the comparison operation to a reflection method of another operand.this File Define the reflected method as:

These methods do not exchange parameter versions (when the left parameter does not support the operation but the right parameter does); instead, u lt_() and u gt_() are reflections of each other, u le_() and u ge_() are reflections of each other, u eq_() u ne_() and u ne_() are their own reflections.

The result looks like this:

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

If the operand is of an unrelated type (no inheritance), the exchangeability of the==and!=operators is required, and it is correct to return the NotImplemented value instead of False even for new classes.

Are we here?Incomplete.How many unique numbers do we have?

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

The collection uses the hash value of the object, and by default, Python returns the hash value of the object identifier.Let's try to overwrite it:

def __hash__(self):
    """Overrides the default implementation"""
    return hash(tuple(sorted(self.__dict__.items())))

len(set([n1, n2, n3])) # 1

The final result is as follows (I added some assertions at the end to verify):

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

#3rd floor

From this answer: https :u ne_u I have proved that although defining u ne_u with u eq_u is correct - not

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

You should use:

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

#4th floor

You do not have to overwrite u eq_ and u ne_u at the same time, you can only overwrite u cmp_u but this will==,!==, <, > and so on.

Is object identity test.This means that a is b will be True if both a and B hold references to the same object.In python, you always have references to objects in variables, not actual objects, so in essence, to make a true for b, the objects in them should be in the same memory location.Most importantly, why do you want to continue to overwhelm this behavior?

Edit: I don't know u cmp_u has been removed from python 3, so please avoid using it.

#5th floor

I think the two terms you're looking for are equality (==) and identity (is).For example:

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

Tags: Python

Posted on Fri, 14 Feb 2020 21:17:57 -0500 by CoffeeOD