Fundamentals 7 min read

Practical Python Comparison and Hashing Magic Methods – 10 Code Examples

This article presents ten practical Python code examples demonstrating how to implement and use comparison and hashing magic methods such as __eq__, __ne__, __lt__, __hash__, and related utilities like functools.total_ordering and memoization for custom objects.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Practical Python Comparison and Hashing Magic Methods – 10 Code Examples

Comparison and hashing are essential concepts in Python, influencing object equality, ordering, and their use in collections. The following ten examples illustrate common scenarios where magic methods are overridden to customize these behaviors.

1. Implement equality comparison (__eq__)

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 False

p1 = Point(1, 2)
p2 = Point(1, 2)
print(p1 == p2)  # True

By overriding __eq__ , objects can define their own equality logic.

2. Implement inequality comparison (__ne__)

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

p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 != p2)  # True

Overriding __ne__ allows custom inequality behavior.

3. Implement less‑than comparison (__lt__)

class Point:
    # ...
    def __lt__(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

Defining __lt__ enables ordering of custom objects.

4. Implement hash value calculation (__hash__)

class Point:
    # ...
    def __hash__(self):
        return hash((self.x, self.y))

p = Point(1, 2)
print(hash(p))  # e.g., 3713081631934410656

Overriding __hash__ makes instances usable as dictionary keys or set elements.

5. Simplify comparison methods with functools.total_ordering

from functools import total_ordering

@total_ordering
class Point:
    # ...
    def __eq__(self, other):
        # ...
    def __lt__(self, other):
        # ...

The total_ordering decorator automatically generates the remaining rich comparison methods.

6. Custom object sorting by defining __lt__

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

    def __lt__(self, other):
        if isinstance(other, Person):
            return self.age < other.age
        return NotImplemented

people = [Person("Alice", 25), Person("Bob", 30), Person("Charlie", 20)]
sorted_people = sorted(people)
for person in sorted_people:
    print(person.name, person.age)

Implementing __lt__ lets sorted() order custom objects.

7. Use custom objects as dictionary keys

class Point:
    # ...
    def __hash__(self):
        return hash((self.x, self.y))

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

data = {p1: "value1", p2: "value2"}
print(data[p1])  # value1

Defining __hash__ (and __eq__ ) enables objects to serve as dictionary keys.

8. Use custom objects as set elements

class Point:
    # ...
    def __eq__(self, other):
        # ...
    def __hash__(self):
        return hash((self.x, self.y))

p1 = Point(1, 2)
p2 = Point(3, 4)
my_set = {p1, p2}
print(len(my_set))  # 2

Providing both __eq__ and __hash__ ensures proper set behavior.

9. Combine __eq__, __lt__, and __hash__ for full comparison and hashing

class Point:
    # ...
    def __eq__(self, other):
        # ...
    def __lt__(self, other):
        # ...
    def __hash__(self):
        return hash((self.x, self.y))

p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = Point(1, 2)
print(p1 == p2)  # False
print(p1 < p2)   # True
my_set = {p1, p2, p3}
print(len(my_set))  # 2

Combining these magic methods gives complete control over equality, ordering, and hashability.

10. Memoization using a custom callable object as a cache key

class Memoize:
    def __init__(self, func):
        self.func = func
        self.cache = {}

    def __call__(self, *args):
        if args in self.cache:
            return self.cache[args]
        result = self.func(*args)
        self.cache[args] = result
        return result

@Memoize
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(10))  # 55

Using a custom object with __call__ and a hashable argument tuple provides an efficient memoization pattern.

These ten scenarios demonstrate how Python’s magic methods for comparison and hashing can be applied to define object equality, ordering, hashability, simplify implementations with functools.total_ordering , and create performant caching mechanisms.

comparisonHashingmemoizationfunctoolsMagic Methods
Test Development Learning Exchange
Written by

Test Development Learning Exchange

Test Development Learning Exchange

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.