🎓 A1Learner
🔍

Python Comparison & Hash Methods

Comparison and hash dunder methods teach Python how objects compare. __eq__ decides when two objects count as equal.

Equality, field by field

Write __eq__ to say when two Versions count as the same release. Comparing tuples compares both fields in one line.

class Version:
    def __init__(self, major, minor):
        self.major = major
        self.minor = minor

    def __eq__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) == (other.major, other.minor)

print(Version(2, 1) == Version(2, 1))
print(Version(2, 1) == Version(2, 2))

Foreign types get NotImplemented

Return NotImplemented when the other value is not a Version. Python then tries the other side, and finally answers False.

print(Version(2, 1) == "2.1")
print(Version(2, 1) != "2.1")

Add a hash that matches equality

Defining __eq__ removes the default hash from your class. Add __hash__ over the same fields that __eq__ compares.

    def __hash__(self):
        return hash((self.major, self.minor))

Equal versions share one dict key

Python finds the key by hash, then checks it with __eq__. Two equal Versions therefore land on the same key.

notes = {Version(2, 1): "current"}
print(notes[Version(2, 1)])
print(len({Version(2, 1), Version(2, 1)}))

One ordering method, with help

@total_ordering is a decorator: a tool that rewrites the class under it. Give the class __lt__, and it builds the other comparisons.

from functools import total_ordering

@total_ordering
class Version:
    # Keep the __eq__ method from earlier in this class.

    def __lt__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) < (other.major, other.minor)

Sorting uses your order

sorted() compares items with your methods. The >= check also works, thanks to total_ordering.

versions = [Version(2, 0), Version(1, 9), Version(2, 1)]
ordered = sorted(versions)
print([(v.major, v.minor) for v in ordered])
print(Version(1, 9) < Version(2, 0))
print(Version(2, 1) >= Version(2, 0))

Example

from functools import total_ordering

@total_ordering
class Version:
    def __init__(self, major, minor):
        self.major = major
        self.minor = minor

    def __repr__(self):
        return f"Version({self.major}, {self.minor})"

    def __eq__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) == (other.major, other.minor)

    def __lt__(self, other):
        if not isinstance(other, Version):
            return NotImplemented
        return (self.major, self.minor) < (other.major, other.minor)

    def __hash__(self):
        return hash((self.major, self.minor))

notes = {Version(1, 9): "old", Version(2, 0): "current"}
print(notes[Version(2, 0)])
versions = [Version(2, 1), Version(1, 9), Version(2, 0)]
print(sorted(versions))
print(Version(2, 1) > Version(2, 0))
print(Version(1, 9) == Version(1, 9))

Key points

  • __eq__: the method Python calls for the == operator
  • NotImplemented: return it to say your class cannot compare with that value
  • Hashable: an object Python can use as a dict key or set member
  • __hash__: returns a number Python uses to find the object quickly
  • Use total_ordering to build every comparison from __eq__ and __lt__

In this example, Version stores a major and a minor number. __eq__ compares both numbers, and returns NotImplemented for other types. __lt__ compares the same pair, so smaller releases sort first. total_ordering builds the remaining comparisons, like > and >=. __hash__ hashes the same pair, so equal Versions share one key. The dict lookup finds "current" with a fresh, equal Version object. sorted() then prints the three versions in release order.

Note

Objects that compare equal must return the same hash.

Break that rule, and dict lookups start missing keys that exist.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Python Playground

Exercise

Two Version objects compare equal with ==. What must be true about their hash values?