AI Skill Report Card

Comparing Python Objects

Explains Python's comparison operators and semantics, including rich comparisons, chained comparisons, and default equality/ordering behavior. Use when writing __eq__/__lt__/__gt__ methods, debugging comparison-related bugs, implementing sortable/comparable classes, or reasoning about how Python compares built-in types like numbers, sequences, and mixed types.

A-87·Aug 12, 2026·Source: Web
14 / 15

Python has 8 comparison operators, all with the same priority and left-to-right chaining:

Python
< <= > >= == != is is not

Default behavior:

  • == / != fall back to identity (is/is not) if no __eq__ is defined.
  • <, <=, >, >= raise TypeError unless the type defines them (no default ordering).
  • Objects are compared using rich comparison methods, not a single __cmp__.

To make a class fully comparable, implement __eq__ and one ordering method, then use functools.total_ordering:

Python
from functools import total_ordering @total_ordering class Version: def __init__(self, major, minor): self.major, self.minor = major, 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)
Recommendation
Description is slightly long/dense; could be tightened to lead with the strongest trigger phrases for discoverability.
13 / 15

When implementing or debugging comparisons on a class:

  • Decide if identity-based default (is) is sufficient — if so, skip overrides.
  • Implement __eq__ returning NotImplemented for incompatible types (not False).
  • Implement __lt__ (and optionally __le__, __gt__, __ge__) — or just __lt__/__eq__ + @total_ordering.
  • Ensure consistency: if a == b, then hash(a) == hash(b) (define __hash__ alongside __eq__, or set __hash__ = None for unhashable/mutable types).
  • Test reflected operations: a < b calls b.__gt__(a) if a.__lt__(b) returns NotImplemented.
  • Check chained comparisons behave correctly (a < b < c is a < b and b < c, each side evaluated once).
Recommendation
Add a concrete example showing __hash__ = None consequence (e.g., TypeError when using object in a set) to reinforce the hashability pitfall.

Rich comparison methods (in order Python tries them):

OperatorMethod
<__lt__
<=__le__
>__gt__
>=__ge__
==__eq__
!=__ne__ (defaults to inverting __eq__)

Reflection rule: For a OP b, if a's method returns NotImplemented, Python tries b's reflected method (<>, <=>=, ==/!= reflect to themselves). If both return NotImplemented, ==/!= fall back to identity; ordering operators raise TypeError.

Built-in type comparison behavior:

  • Numbers (int, float, complex, Decimal, Fraction): compared by mathematical value across types (1 == 1.0 is True); complex only supports ==/!=, not ordering.
  • Sequences (same type, e.g. two lists or two tuples): compared lexicographically — element-by-element, first mismatch decides, shorter-is-less if one is a prefix of the other.
  • Mixed sequence types (e.g. list vs tuple): ==/!= work structurally only for same type; ordering between different sequence types raises TypeError.
  • str, bytes, bytearray: lexicographic by underlying code points/byte values. str and bytes are never equal or orderable to each other.
  • dict: ==/!= compare keys and values regardless of order; no ordering operators.
  • set/frozenset: == is set equality; <, <=, >, >= mean subset/superset (partial order, not total — some sets are simply "not comparable" and both < and > return False).
  • NaN: float('nan') != float('nan') and all ordering comparisons with NaN are False.
17 / 20

Example 1: Chained comparison pitfall Input:

Python
1 < 2 < 3

Output: True — equivalent to (1 < 2) and (2 < 3), 2 evaluated once, not (1 < 2) < 3 (True < 3).

Example 2: Mixed sequence ordering Input:

Python
[1, 2] < (1, 2)

Output: TypeError: '<' not supported between instances of 'list' and 'tuple' — ordering requires same/compatible types even though == between them (structurally) would just be False, not an error.

Example 3: Set partial order Input:

Python
{1, 2} < {2, 3}

Output: False (not a subset) — and {1, 2} > {2, 3} is also False. Sets are a partial order, so "not <" does not imply ">=".

Example 4: NotImplemented vs False Input: A custom __eq__ compares against an unrelated type and returns False instead of NotImplemented. Output: Breaks reflection — if the other object's __eq__ would have returned True, Python never gets the chance to try it, since False short-circuits the comparison as "handled."

Recommendation
Consider a short example contrasting subclass comparison behavior (reflected method priority for subclasses) since it's mentioned in the workflow but not shown in examples.
  • Always return NotImplemented (not False/True) from comparison methods when the other operand's type is unsupported — this lets Python try the reflected method.
  • Use functools.total_ordering instead of hand-writing all six comparison methods.
  • If you define __eq__, explicitly decide on __hash__: inherit/reuse it if the object is still hashable, or set __hash__ = None if mutable/unhashable.
  • Prefer == over is for value comparisons; reserve is for singleton checks (None, True, False, enum members).
  • Remember sequence/number comparisons are element-wise/value-wise, not identity-wise — don't assume == implies is.
  • Comparing NaN and expecting equality/reflexivity — nan == nan is False by IEEE 754 design.
  • Assuming all types support ordering — many (complex numbers, dicts, mixed sequence types) only support ==/!=.
  • Returning False instead of NotImplemented in custom __eq__/__lt__, silently breaking comparisons with subclasses or duck-typed objects.
  • Forgetting that != doesn't automatically invert a custom __eq__ in old-style implementations — in modern Python it does by default, but overriding __eq__ without checking __ne__ behavior in edge cases (e.g., returning NotImplemented) can cause inconsistencies.
  • Treating set </> as a total order — two sets can be simultaneously "not less than" and "not greater than" each other.
0
Grade A-AI Skill Framework
Scorecard
Criteria Breakdown
Quick Start
14/15
Workflow
13/15
Examples
17/20
Completeness
18/20
Format
13/15
Conciseness
14/15