Compare object instances for equality by their attributes
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
When you compare two objects in Python using ==, the default behavior checks whether they are the exact same object in memory (reference equality). In most real-world applications, you want to know whether two objects have the same attribute values instead (value equality). This article explains how to implement attribute-based equality comparison in Python, covers the relationship between __eq__ and __hash__, and walks through common mistakes.
Reference Equality vs. Value Equality
By default, Python's == operator for custom classes checks identity, which is the same as using is:
Even though p1 and p2 have identical attribute values, they are two separate objects and the comparison returns False. To fix this, you need to define the __eq__ method.
Implementing __eq__ for Value Equality
The __eq__ method lets you define what "equal" means for instances of your class:
Returning NotImplemented instead of False when other is a different type allows Python to try the comparison from the other side. This is important for interoperability with subclasses and other types.
The __eq__ and __hash__ Relationship
In Python, when you define __eq__, the default __hash__ method is automatically set to None. This means objects of your class become unhashable and cannot be used as dictionary keys or set members:
To restore hashability, implement __hash__ using the same attributes that __eq__ compares:
The rule is: objects that compare equal must have the same hash value. If __eq__ uses x and y, then __hash__ must also use x and y.
Using dataclasses for Automatic Equality
Python 3.7+ provides dataclasses that generate __eq__ (and optionally __hash__) automatically:
By default, @dataclass generates __eq__ but not __hash__ (because instances are mutable). To make instances hashable, use frozen=True:
Frozen dataclasses are immutable and hashable, making them ideal for use as dictionary keys or set members.
Comparing All Attributes Generically
If you want to compare all attributes without listing each one, you can use __dict__:
This is convenient but has caveats. It compares every attribute, including ones you may want to exclude (like internal caches or timestamps). It also does not work with __slots__ classes.
Equality with Inheritance
When your classes form an inheritance hierarchy, equality checks need extra care:
Using type(other) is not type(self) instead of isinstance prevents a Shape from being considered equal to a Circle that happens to have the same color. This preserves symmetry: if a == b, then b == a.
Common Pitfalls
- Forgetting
__hash__: Defining__eq__without__hash__makes your objects unusable in sets and as dictionary keys. Python will raise aTypeError. - Mutable attributes in
__hash__: If you include mutable attributes in__hash__and then change them, the object becomes "lost" in any set or dict it was added to. - Breaking symmetry with
isinstance: Usingisinstancein__eq__can create asymmetric comparisons between parent and child classes. - Comparing floats directly: Floating-point attributes should be compared with a tolerance (using
math.isclose()) rather than strict equality.
Summary
To compare objects by their attributes in Python, implement __eq__ to define what equality means for your class. Always implement __hash__ alongside __eq__ if you need your objects in sets or dictionaries. For simple data-holding classes, use @dataclass to get __eq__ for free. Be mindful of symmetry, transitivity, and the implications of inheritance when designing equality logic.

