Back to Blog
Python

Python Dataclass eq: Controlling Equality and Hashing

The eq parameter in Python dataclasses controls __eq__ generation, affects __hash__, and interacts with frozen and order. See when to use eq=False for identity equality.

pythondataclassesequalityhashingobject comparison
Diagram showing how the eq parameter in a Python dataclass toggles equality method generation and affects hashing behavior.

The eq parameter in a Python dataclass controls whether the generated class includes an __eq__ method. By default, eq=True, so dataclasses compare by field values. The setting also affects __hash__ and interacts with frozen and order.

What the eq Parameter Controls

When you use @dataclass, Python generates methods based on the parameters you pass. With eq=True (the default), the dataclass gets an __eq__ that compares instances by their fields, in order. The generated comparison also requires the other object to be the same dataclass type; otherwise it returns NotImplemented. With eq=False, no __eq__ is generated, so, unless a base class defines its own __eq__, the class uses the identity-based equality inherited 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

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 eq=True and the dataclass generates __eq__, it sets __hash__ to None unless frozen=True or unsafe_hash=True is also specified. If you define an explicit __hash__, the dataclass does not replace it. With the common default case, this makes instances unhashable.

@dataclass class UnhashablePoint: x: int y: int p = UnhashablePoint(1, 2) # hash(p) raises TypeError: unhashable type: 'UnhashablePoint'

On its own, eq=False does not change __hash__. The dataclass does not generate __eq__, and it leaves __hash__ alone unless you also set unsafe_hash=True. In the usual case, the class therefore keeps the default identity-based __hash__ from object.

@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, set frozen=True (with the default eq=True). For a frozen dataclass, dataclasses generates __hash__ from the field values, and instance attribute assignment is blocked after creation. The hash remains stable as long as the field values themselves are hashable and do not change. If immutability is not suitable, unsafe_hash=True can generate a hash for a non-frozen dataclass, but you must avoid mutating instances after they are used as dictionary keys or set members.

@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.

@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, dataclasses generates a __hash__ method from the field values. 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

Set eq=False when you want instances to use identity-based equality rather than value equality. If you are planning to define a custom __eq__, you do not need eq=False: dataclasses do not replace a __eq__ defined in the class body. eq=False is useful, though, when you need to keep an __eq__ inherited from a base class, because with the default eq=True the dataclass would generate its own and hide the inherited method. The important part is to also define a compatible __hash__, because Python sets __hash__ to None when a class defines __eq__ without also defining __hash__.

@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))

Using eq=False here is compatible with the manual methods; the manual methods define the equality and hash behavior. You also provide a compatible __hash__ to maintain the invariant that equal objects have equal hashes.

Practical 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.

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.

python dataclass eq: Practical Usage and Code Examples | RYUSLOG DEV