__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:
- First, Python calls
a.__eq__(b) - If that returns
NotImplemented, Python callsb.__eq__(a) - 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
| Method | Operator | Default behavior |
|---|---|---|
__eq__ | == | Identity comparison (is) |
__ne__ | != | Negation of __eq__ (Python 3) |
Key takeaways:
- Define
__eq__to customize what equality means for your objects - Return
NotImplementedfor unsupported types — neverFalse - 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