Fundamentals 7 min read

Python Comparison and Hash Magic Methods: Practical Code Examples

This article explains Python's comparison and hash magic methods with ten practical code snippets, covering equality, inequality, ordering, hash calculation, total_ordering simplification, custom object sorting, and using objects as dictionary keys, set elements, and memoization caches.

Test Development Learning Exchange
Test Development Learning Exchange
Test Development Learning Exchange
Python Comparison and Hash Magic Methods: Practical Code Examples

Comparison and hashing are core concepts in Python that affect object equality, ordering, and their use in collections such as dictionaries and sets. The following examples demonstrate how to implement and leverage the related magic methods.

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

Overriding __eq__ defines how two objects are considered equal.

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

Implementing __ne__ allows custom inequality behavior.

3. 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 comparisons.

4. Hash value calculation (__hash__)

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

p = Point(1, 2)
print(hash(p))  # Example output: 3713081631934410656

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

5. Simplifying comparisons 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 supplies the remaining ordering methods ( __ne__ , __gt__ , __le__ , __ge__ ) based on __eq__ and __lt__ .

6. Custom object sorting

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. Using 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. Using 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 set elements are unique.

9. Combined use of __eq__, __lt__, and __hash__

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 methods gives full control over equality, ordering, and hash behavior.

10. Memoization with a custom object as 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 as a cache key avoids redundant calculations and speeds up recursive functions.

These ten code snippets illustrate practical applications of Python's comparison and hash magic methods, covering equality, inequality, ordering, hash generation, total_ordering simplification, custom sorting, and using objects as dictionary keys, set elements, and memoization caches.

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