ProgrammingBackend Developer

How does the object comparison protocol work in Python (__eq__, __ne__, __lt__, __le__, __gt__, __ge__)? Why are all these methods needed and what can improper implementation lead to?

Pass interviews with Hintsage AI assistant

Answer.

Background

Python has allowed comparing objects since its early versions. To describe what "greater" or "equal" means for your objects, special comparison methods (eq, ne, lt, le, gt, ge) were created. With the introduction of PEP 207 (Python 2.1+), a unified comparison protocol was implemented, allowing programmers to explicitly control behavior during comparisons.

Problem

Standard types (int, str) are compared "by value", but user-defined classes are compared "by identity" (is) by default. If comparison magic methods are not implemented or are implemented incorrectly, comparing your objects will lead to unexpected behavior when working with collections, sorting, using set and dict, and may sometimes result in type errors.

Solution

You need to explicitly define comparison methods in your class. Typically, eq (equality) and lt (less than) are implemented, while the other methods can be automatically provided using the functools.total_ordering decorator.

Code example:

from functools import total_ordering @total_ordering 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 def __lt__(self, other): if not isinstance(other, Point): return NotImplemented return (self.x, self.y) < (other.x, other.y) # Usage: p1 = Point(1, 2) p2 = Point(1, 3) print(p1 < p2) # True print(p1 == p2) # False

Key features:

  • It's necessary to return NotImplemented, not TypeError, if comparing with an unknown type
  • Incorrect implementation of one method (e.g., eq) without the corresponding hash will lead to failure when working with sets/dicts
  • functools.total_ordering simplifies the implementation of a complete set of comparisons

Trick questions.

Can you implement only one comparison method (eq or lt), and everything will work?

No. Implementing only eq will ensure correct equality comparison, but operators <, >, <=, >= will behave in the default way (typically through object identity comparison). To achieve full behavior, you should implement a sufficient set of methods or use functools.total_ordering.

Why doesn't compare(x, y) exist in Python, as in other languages?

Python inherited the idea of special "magic" methods for each operator, and the cmp function was removed starting from Python 3. For general object comparisons, rich comparison methods (lt, eq, etc.) are used, and for sorting, the key parameter in sort/sorted functions is used.

What happens if you compare an object with a type for which the required method isn't implemented?

If NotImplemented is returned in eq or other methods for unsupported types, Python will automatically try the reverse method on the second operand or return False. If such a case is not handled, a TypeError or incorrect behavior may occur.

Code example:

class Foo: def __eq__(self, other): return NotImplemented class Bar: pass print(Foo() == Bar()) # False (tries through Bar.__eq__)

Common mistakes and anti-patterns

  • Forgetting to implement hash when overriding eq — objects cannot be used as dictionary keys
  • Returning True/False for any type without type checking yields unwanted matches
  • Violating symmetry: for example, x == y is True, but y == x is False

Real-world example

Negative case

A developer created a User class and implemented only eq without hash, so users were considered equal based on email. Then they tried to use User objects in a set/as dictionary keys.

Pros:

  • The equality check logic works with direct comparison

Cons:

  • Objects cannot be added to a set; TypeError: unhashable type: 'User' occurs
  • Only ==/!= checks work; a set or dictionary always considers the objects different

Positive case

A programmer uses @total_ordering and implements eq and lt with type checking, and also adds hash, if the class is immutable. Objects are compared correctly, sorted, and work as dictionary keys.

Pros:

  • Simple code, little duplication
  • Reliable behavior in all collections and sorts

Cons:

  • @total_ordering may be slightly slower than explicitly implementing all methods for critical classes