Python Dataclass eq: Controlling Equality and Hashing
python dataclass eq: Learn how the eq parameter in Python dataclasses controls __eq__ generation, affects hashing, and interacts with frozen and order settings.
python dataclass eq requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
The eq parameter in a Python dataclass determines whether the generated class includes an __eq__ method. By default, eq=True, which means dataclasses compare by field values. But this decision also influences __hash__ and interacts with other dataclass options like frozen and order.
What the eq Parameter Controls
When you declare a dataclass with @dataclass, Python generates several methods based on the parameters you pass. The eq parameter specifically controls the generation of __eq__. With eq=True (the default), the dataclass gets an __eq__ that compares instances by their fields, in order. With eq=False, no __eq__ is generated, so the class falls back to the default identity-based equality from object.
from dataclasses import dataclass @dataclass class Point: x: int y: int @dataclass(eq=False) class RawPoint: x: int y: int p1 = Point(1, 2) p2 = Point(1, 2) print(p1 == p2) # True r1 = RawPoint(1, 2) r2 = RawPoint(1, 2) print(r1 == r2) # False, identity comparison
The eq parameter is a boolean that directly toggles the generation of the equality method. This is the core behavior you need to understand when deciding how your dataclass instances should be compared.
How eq Affects hash
The relationship between eq and __hash__ is one of the most important details. Python's data model requires that objects that compare equal must have the same hash value. To enforce this, when a dataclass defines __eq__ (i.e., eq=True), it sets __hash__ to None unless frozen=True is also specified. This makes instances unhashable by default.
@dataclass class UnhashablePoint: x: int y: int p = UnhashablePoint(1, 2) # hash(p) raises TypeError: unhashable type: 'UnhashablePoint'
If you set eq=False, the dataclass does not generate __eq__, so it also does not touch __hash__. The class inherits __hash__ from object, which is based on object identity, and instances remain hashable.
@dataclass(eq=False) class HashablePoint: x: int y: int h = HashablePoint(1, 2) print(hash(h)) # integer based on id()
If you need both value equality and hashability, you must set frozen=True. A frozen dataclass is immutable, so its hash can be computed from the fields and will remain stable.
@dataclass(frozen=True) class FrozenPoint: x: int y: int f = FrozenPoint(1, 2) print(hash(f)) # hash based on field values
This interaction is often the reason developers explicitly set eq=False: to preserve the default identity-based hashing while still using the dataclass for its other conveniences.
Using eq=False to Preserve Identity Equality
There are scenarios where you want dataclass instances to be compared by identity, not by value. For example, when modeling entities that have a unique identity independent of their fields, such as database records or mutable objects that should not be considered equal just because their current state matches.
@dataclass(eq=False) class User: id: int name: str u1 = User(1, "Alice") u2 = User(1, "Alice") print(u1 == u2) # False, they are different objects
This is useful when you rely on object identity for caching, dictionary keys, or set membership, and you want to avoid accidental value-based collisions. It also keeps the class hashable, which is often a side benefit.
Combining eq with order and frozen
The order parameter in dataclasses requires eq=True. If you set order=True but eq=False, you get a ValueError at class definition time. This is because ordering comparisons (<, <=, >, >=) are built on top of equality in the generated methods.
@dataclass(eq=False, order=True) class BadOrdered: x: int
Raises: ValueError: eq must be True if order is True.
When frozen=True is combined with eq=True, the dataclass becomes hashable because it is immutable. This combination is common for value objects that need to be used as dictionary keys or stored in sets.
@dataclass(frozen=True, order=True) class Version: major: int minor: int v1 = Version(1, 0) v2 = Version(1, 0) print(v1 == v2) # True print(v1 < v2) # False print(hash(v1)) # stable hash
Understanding these interactions helps you avoid runtime surprises when you change one parameter without considering the others.
When to Set eq=False
You should set eq=False when you need the default identity-based equality, or when you plan to define a custom __eq__ method manually. If you define a custom __eq__, you must also handle __hash__ appropriately, because Python will set __hash__ to None if you define __eq__ in the class body unless you explicitly define it.
@dataclass(eq=False) class CustomEq: data: list def __eq__(self, other): if not isinstance(other, CustomEq): return NotImplemented return self.data == other.data def __hash__(self): return hash(tuple(self.data))
Here, eq=False prevents the dataclass from generating its own __eq__, allowing your custom method to take precedence. You also provide a compatible __hash__ to maintain the invariant that equal objects have equal hashes.
Another common reason is when you want to use dataclasses as simple data containers without the overhead of field-by-field comparison, especially if you know that identity comparison is sufficient for your use case.
Runtime and Maintainability Considerations
The choice of eq has direct consequences for how your objects behave in sets, dictionaries, and equality checks. If you leave eq=True and forget that __hash__ is set to None, you may encounter TypeError when trying to use instances as dictionary keys. This is a common source of bugs in codebases that add dataclasses to sets or use them as keys without setting frozen=True.
On the maintainability side, explicitly setting eq=False documents your intent that instances are compared by identity. This can prevent future developers from assuming value equality. Conversely, using eq=True with frozen=True clearly signals an immutable value object.
The runtime cost of eq=True is minimal: the generated __eq__ compares fields using tuple comparison, which is efficient for small numbers of fields. The main cost is the potential for accidental unhashability, which is a design issue rather than a performance one.
When you need to store dataclass instances in a set or as dictionary keys, prefer frozen=True with eq=True to get value-based hashing. When you need identity semantics, use eq=False to keep the default hash.