Python __eq__ Method
Last modified April 8, 2025
This comprehensive guide explores Python's __eq__
method, the
special method responsible for equality comparison. We'll cover basic usage,
custom comparisons, hash consistency, inheritance, and practical examples.
Basic Definitions
The __eq__
method defines behavior for the equality operator (==).
It should return True
if objects are equal, False
otherwise, or NotImplemented
if comparison isn't supported.
Key characteristics: it takes two parameters (self and other), should implement
reflexive, symmetric, and transitive properties, and often works with
__hash__
for consistent behavior in collections.
Basic __eq__ Implementation
Here's a simple implementation showing how __eq__
works for
comparing objects. We'll create a Point class that compares coordinates.
class Point: def __init__(self, x, y): self.x = x self.y = y def __eq__(self, other): if not isinstance(other, Point): return NotImplemented return self.x == other.x and self.y == other.y p1 = Point(1, 2) p2 = Point(1, 2) p3 = Point(3, 4) print(p1 == p2) # True print(p1 == p3) # False print(p1 == "not a point") # False (uses NotImplemented)
This example shows basic equality comparison between Point instances. The
__eq__
method checks if the other object is a Point and compares
coordinates. For non-Point objects, it returns NotImplemented
.
The NotImplemented
return allows Python to try the reverse
operation or fall back to default behavior. This maintains proper comparison
semantics.
Custom Equality with Business Logic
__eq__
can implement business-specific equality rules. Here we
compare BankAccount objects by account number, ignoring balance differences.
class BankAccount: def __init__(self, account_number, balance): self.account_number = account_number self.balance = balance def __eq__(self, other): if not isinstance(other, BankAccount): return NotImplemented return self.account_number == other.account_number def __repr__(self): return f"BankAccount({self.account_number}, ${self.balance})" acc1 = BankAccount("12345", 1000) acc2 = BankAccount("12345", 500) acc3 = BankAccount("67890", 1000) print(acc1 == acc2) # True (same account number) print(acc1 == acc3) # False
This implementation considers accounts equal if they have the same account number, regardless of balance. This might be useful for checking account existence in a system.
Note how we still check the type of other
to maintain comparison
safety. The __repr__
method helps with debugging but doesn't
affect equality comparison.
Equality with Inheritance
When dealing with inheritance, __eq__
requires careful
implementation to maintain proper comparison semantics between parent and
child classes.
class Vehicle: def __init__(self, make, model): self.make = make self.model = model def __eq__(self, other): if not isinstance(other, Vehicle): return NotImplemented return self.make == other.make and self.model == other.model class Car(Vehicle): def __init__(self, make, model, doors): super().__init__(make, model) self.doors = doors def __eq__(self, other): if not isinstance(other, Car): return NotImplemented return super().__eq__(other) and self.doors == other.doors v1 = Vehicle("Toyota", "Camry") c1 = Car("Toyota", "Camry", 4) c2 = Car("Toyota", "Camry", 2) print(v1 == c1) # False (different types) print(c1 == c2) # False (different doors)
This example shows proper equality handling in an inheritance hierarchy. The Car class extends Vehicle's equality check with its own attribute comparison.
Note that Vehicle
and Car
instances are never equal,
even if they share make and model. This maintains the Liskov substitution
principle by not treating different types as equal.
Maintaining Hash Consistency
When overriding __eq__
, you should also consider __hash__
to maintain proper behavior in dictionaries and sets. Here's how to do it.
class Product: def __init__(self, id, name, price): self.id = id self.name = name self.price = price def __eq__(self, other): if not isinstance(other, Product): return NotImplemented return self.id == other.id def __hash__(self): return hash(self.id) def __repr__(self): return f"Product({self.id}, '{self.name}', {self.price})" p1 = Product(1, "Laptop", 999) p2 = Product(1, "Laptop Pro", 1299) products = {p1, p2} print(products) # Only contains one product (same ID)
This example shows how to maintain hash consistency when overriding equality. Objects that compare equal must have the same hash value to work correctly in hash-based collections.
We use the product ID for both equality comparison and hashing, ensuring consistent behavior. The name and price changes don't affect equality or hashing in this implementation.
Case-Insensitive String Comparison
__eq__
can implement alternative comparison logic, like
case-insensitive string comparison in a custom string class.
class CaseInsensitiveString: def __init__(self, value): self.value = value def __eq__(self, other): if isinstance(other, str): return self.value.lower() == other.lower() if not isinstance(other, CaseInsensitiveString): return NotImplemented return self.value.lower() == other.value.lower() def __str__(self): return self.value s1 = CaseInsensitiveString("Hello") s2 = CaseInsensitiveString("hello") print(s1 == s2) # True print(s1 == "HELLO") # True print("WORLD" == CaseInsensitiveString("world")) # True
This class implements case-insensitive comparison with both its own instances
and regular strings. The __eq__
method handles both cases by
converting strings to lowercase before comparison.
Note how it works in both directions (a == b and b == a) because we handle
string comparison directly and return NotImplemented
for other
types, allowing Python to try the reverse operation.
Best Practices
- Type checking: Always verify the type of the other object
- Return NotImplemented: For unsupported comparisons
- Maintain hash consistency: Override __hash__ when overriding __eq__
- Follow comparison laws: Ensure reflexivity, symmetry, and transitivity
- Consider performance: Implement efficient comparison logic
Source References
Author
List all Python tutorials.