pyguides

__eq__ / __ne__

__eq__(self, other)
__ne__(self, other)

Overview

The __eq__ method defines the behavior of the equality operator (==) for your class. The __ne__ method defines the behavior of the not-equal operator (!=).

By default, Python compares objects by identity — whether two variables point to the same object in memory. This default applies to every user-defined class unless you override __eq__. Python 3 automatically derives __ne__ from __eq__ by inverting the result, so you rarely need to define __ne__ explicitly.

Signatures

def __eq__(self, other) -> bool | NotImplemented:
    ...

def __ne__(self, other) -> bool | NotImplemented:
    ...

Both methods return a boolean or NotImplemented. Returning NotImplemented tells Python to try the comparison on the other operand instead.

Basic Example

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return NotImplemented

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2)  # True — same values
# output: True
print(p1 == p3)  # False — different values
# output: False

How comparison works

When Python evaluates a == b, it follows a specific fallback chain:

  1. First, Python calls a.__eq__(b)
  2. If that returns NotImplemented, Python calls b.__eq__(a)
  3. If both return NotImplemented, Python falls back to identity comparison (a is b)

This chain matters when you compare objects of different types:

class Wrapper:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, Wrapper):
            return self.value == other.value
        if isinstance(other, (int, float)):
            return self.value == other
        return NotImplemented

w = Wrapper(42)
print(w == 42)     # True — Python tries int.__eq__(Wrapper(42)) which returns NotImplemented, then Wrapper.__eq__ handles it
# output: True
print(w == "hello")  # False — both return NotImplemented, falls back to identity
# output: False

The __ne__ Method

Python 3 automatically derives __ne__ from __eq__. If you define __eq__ but not __ne__, Python inverts the __eq__ result:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        if isinstance(other, Point):
            return self.x == other.x and self.y == other.y
        return NotImplemented

p1 = Point(1, 2)
p2 = Point(3, 4)

print(p1 != p2)  # True — Python derives this from __eq__
# output: True

Define __ne__ explicitly when the negation of __eq__ doesn’t capture your intent:

class User:
    def __init__(self, name, is_active):
        self.name = name
        self.is_active = is_active

    def __eq__(self, other):
        if isinstance(other, User):
            return self.name == other.name
        return NotImplemented

    def __ne__(self, other):
        if isinstance(other, User):
            return self.name != other.name
        return NotImplemented

__eq__ and __hash__

This catches many developers off guard. If you define __eq__ but don’t explicitly define __hash__, Python sets __hash__ to None, making your objects unhashable:

class Person:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return isinstance(other, Person) and self.name == other.name

# TypeError: unhashable type 'Person'
# people = {Person("Alice"), Person("Bob")}

Python does this because it assumes equal objects should have the same hash value. Without a custom __hash__, Python can’t guarantee this contract when you’ve changed equality semantics.

To make hashable objects with custom equality:

class Person:
    def __init__(self, name):
        self.name = name

    def __eq__(self, other):
        return isinstance(other, Person) and self.name == other.name

    def __hash__(self):
        return hash(self.name)

alice = Person("Alice")
print(hash(alice))
# output: -...
print({alice})  # Works — can use in sets and as dict keys
# output: {<__main__.Person object at ...>}

Common Mistakes

Checking types unsafely

# Wrong — raises AttributeError if other has no 'age'
def __eq__(self, other):
    return self.age == other.age

# Correct — always check type first
def __eq__(self, other):
    if not isinstance(other, Person):
        return NotImplemented
    return self.age == other.age

using False where NotImplemented belongs

# Wrong — prevents Python from trying the reverse comparison
def __eq__(self, other):
    if not isinstance(other, Person):
        return False  # Blocks fallback chain!
    return self.age == other.age

# Correct — lets Python try other.__eq__(self)
def __eq__(self, other):
    if not isinstance(other, Person):
        return NotImplemented
    return self.age == other.age

Assuming self == self always works

Python guarantees reflexive comparison — self == self always returns True for any object, even if your __eq__ is broken. Don’t add special handling for this case.

Full Example

from dataclasses import dataclass

@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity: int = 0

    def total_value(self) -> float:
        return self.unit_price * self.quantity

    def __eq__(self, other):
        if not isinstance(other, InventoryItem):
            return NotImplemented
        return (
            self.name == other.name
            and self.unit_price == other.unit_price
            and self.quantity == other.quantity
        )

    def __hash__(self):
        return hash((self.name, self.unit_price, self.quantity))

item1 = InventoryItem("Widget", 9.99, 10)
item2 = InventoryItem("Widget", 9.99, 10)
item3 = InventoryItem("Gadget", 14.99, 5)

print(item1 == item2)  # True — same name, price, quantity
# output: True
print(item1 == item3)  # False — different name
# output: False
print(item1 in {item2})  # True — hashable and equal
# output: True

Summary

MethodOperatorDefault behavior
__eq__==Identity comparison (is)
__ne__!=Negation of __eq__ (Python 3)

Key takeaways:

  • Define __eq__ to customize what equality means for your objects
  • Return NotImplemented for unsupported types — never False
  • Defining __eq__ without __hash__ makes objects unhashable
  • Python 3 auto-derives __ne__ from __eq__ — explicit __ne__ is rarely needed
  • Always check types before accessing attributes in __eq__

See Also

  • hash() — Returns hash value; related to __eq__ since custom equality requires custom hashing
  • dunder-hash — Defines hashability; closely tied to custom equality
  • dunder-str — Defines str() behavior; another common customization for object display