You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Breaking change of dataclasses.dataclass comparison semantics in 3.13+ #128294
Optimization done in #109870 changed semantic of dataclass comparison.
Description
The pseudo code below shows meaning of changes have been done in #109870. The semantic differs largely because of a shortcut in __eq__ implementation of sequence-like containers (see Objects/object.c). The shortcut essentially does self[i] is other[i]. Consequently, method __eq__ of self[i] is not evaluated for identical objects in 3.12 during dataclasses.dataclass comparison.
According Python docs (citation below), v3.13 introduces breaking change since it does not consider fields as a tuples for dataclass comparison.
eq: If true (the default), an __eq__() method will be generated. This method compares the class as if it were a tuple of its fields, in order. Both instances in the comparison must be of the identical type.
Test Case
importnumpyasnpfromdataclassesimportdataclass@dataclassclassA:
xs: np.ndarraya=A(np.ones(3))
b=A(a.xs)
print(a==b) # FAIL (3.13); OK (3.12).# ValueError: Value The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
changed the title [-]Breaking change of `dataclass.dataclass` comparison semtatics[/-][+]Breaking change of `dataclasses.dataclass` comparison semtatics[/+]on Dec 27, 2024
Indeed, we have the problem of two chairs here. From my perspective, the right decision would be reverting as soon as possible until major Linux distros do not adopt 3.13 widely.
In advance, I could give some context on a domain where the issue araises first. It is machine learning where data classes are used a lot for neural network representation and maintaining weight collections. Comparison is broken there because overridden __eq__ has vector rather scalar semantic.
changed the title [-]Breaking change of `dataclasses.dataclass` comparison semtatics[/-][+]Breaking change of `dataclasses.dataclass` comparison semantics[/+]on Dec 27, 2024
method eq of self[i] is not evaluated for identical objects in 3.12
Considering that we largely short-circuit equality in general, I think we should revert it. In addition, we are now out-of-sync with "This method compares the class as if it were a tuple of its fields, in order" (emphasis mine). While this can be a breaking change, we haven't updated the documentation, so maybe it hasn't been observed.
Most of the time, two objects that are identical (in terms of pointers) should also compare equal, independently of whether there is a custom __eq__ or not, though this is my own opinion. If we're worried about constructing the tuple, instead of doing x[i] == y[i], we could do x[i] is y[i] or x[i] == y[i] to emulate this behaviour but I wonder how performance would be affected.
changed the title [-]Breaking change of `dataclasses.dataclass` comparison semantics[/-][+]Breaking change of `dataclasses.dataclass` comparison semantics in 3.13+[/+]on Dec 27, 2024
From perspective of PEP-557, data classes should be compared as tuples since it explicitly states that Data Classes can be thought of as “mutable namedtuples with defaults”. I guess that making data classes as close as possible to named tuples was a primary design goal. For this reason, breaking change inroduced by #120645 was not the wisest decision.
On other hand, we rarely write (self.x is other.x or self.x == other.x) and (self.y is other.y or self.y == other.y) in hand-made classes. We usually simply write self.x == other.x and self.y == other.y. And dataclasses are intended to remove boilerplate for struct-like classes. namedtuple use identity check because they are tuples, and tuples use identity check because they are sequences and for consistency with the identity check in __contains__. But dataclasses usually are not sequences.
Perhaps in future we will add a per-field boolean option for using identity check.
I think that in this case (no revert) we need to update the docs to reflect the behaviour change. The OP has several examples of older wordings like:
- *eq*: If true (the default), an :meth:`~object.__eq__` method will be
generated. This method compares the class as if it were a tuple
of its fields, in order.
Indeed, dataclasses usually are not sequences but they are fancy sequences. At least, it was design goal according to PEP-557. Anyway, I'd like to hear @ericvsmith opinion on this matter since he initially drafted data classes.
Another consequence of this breaking change is compatibility with third party data class libraries (e.g. general-purpose attrs and pydantic or domain purpose chex or flax.struct).
From my perspective, new semantic of __eq__ is viable change in general but it requires notice period. Third parties and a lot of users appeared in six years since PEP-557 rely on the existing semantics. It will take huge community effort to bring consistency.
I think it's unfortunate that there wasn't a warning of this change. But at this point it has been released, and rolling it back is problematic. Maybe a better notice in the release notes would be enough?
I don't think the PEP comment dataclasses of can be thought of as “mutable namedtuples with defaults” implies that they should be implemented with tuple semantics where possible. The statement is a very high level overview of the functionality.
Another consequence of this breaking change is compatibility with third party data class libraries (e.g. general-purpose attrs and pydantic or domain purpose chex or flax.struct).
Actually, attrs also made a similar breaking change in 24.1.0:
The generated eq methods have been sped up significantly by generating a chain of attribute comparisons instead of constructing and comparing tuples. This change arguably makes the behavior more correct, but changes it if an attribute compares equal by identity but not value, like float('nan'). python-attrs/attrs#1310
So now new attrs and new dataclasses agree, at least.
However, unlike dataclasses.field, attrs.field allows you to pass per-field equality overrides which helps. But otherwise for dataclasses, what we have as a workaround is
def __eq__(self, value: object) -> bool:
if not isinstance(value, type(self)):
return NotImplemented
return astuple(self) == astuple(value)
Activity
[-]Breaking change of `dataclass.dataclass` comparison semtatics[/-][+]Breaking change of `dataclasses.dataclass` comparison semtatics[/+]sobolevn commentedon Dec 27, 2024
In 3.12 we used to do something like:
return (self.x, self.y) == (other.x, other.y), seecpython/Lib/dataclasses.py
Lines 1085 to 1094 in 3a726be
Now, instead we do:
self.x == other.x and self.y == other.y, which triggers explicit__eq__.Problems:
daskol commentedon Dec 27, 2024
Indeed, we have the problem of two chairs here. From my perspective, the right decision would be reverting as soon as possible until major Linux distros do not adopt 3.13 widely.
In advance, I could give some context on a domain where the issue araises first. It is machine learning where data classes are used a lot for neural network representation and maintaining weight collections. Comparison is broken there because overridden
__eq__has vector rather scalar semantic.[-]Breaking change of `dataclasses.dataclass` comparison semtatics[/-][+]Breaking change of `dataclasses.dataclass` comparison semantics[/+]picnixz commentedon Dec 27, 2024
Considering that we largely short-circuit equality in general, I think we should revert it. In addition, we are now out-of-sync with "This method compares the class as if it were a tuple of its fields, in order" (emphasis mine). While this can be a breaking change, we haven't updated the documentation, so maybe it hasn't been observed.
Most of the time, two objects that are identical (in terms of pointers) should also compare equal, independently of whether there is a custom
__eq__or not, though this is my own opinion. If we're worried about constructing the tuple, instead of doingx[i] == y[i], we could dox[i] is y[i] or x[i] == y[i]to emulate this behaviour but I wonder how performance would be affected.cc @Yhg1s as the 3.13 RM.
[-]Breaking change of `dataclasses.dataclass` comparison semantics[/-][+]Breaking change of `dataclasses.dataclass` comparison semantics in 3.13+[/+]sobolevn commentedon Dec 27, 2024
I will send a fix ASAP.
ericvsmith commentedon Dec 27, 2024
See also #120645. I don't think that we could back out the change at this point.
daskol commentedon Dec 28, 2024
From perspective of PEP-557, data classes should be compared as tuples since it explicitly states that Data Classes can be thought of as “mutable namedtuples with defaults”. I guess that making data classes as close as possible to named tuples was a primary design goal. For this reason, breaking change inroduced by #120645 was not the wisest decision.
serhiy-storchaka commentedon Dec 30, 2024
On other hand, we rarely write
(self.x is other.x or self.x == other.x) and (self.y is other.y or self.y == other.y)in hand-made classes. We usually simply writeself.x == other.x and self.y == other.y. Anddataclassesare intended to remove boilerplate for struct-like classes.namedtupleuse identity check because they are tuples, and tuples use identity check because they are sequences and for consistency with the identity check in__contains__. But dataclasses usually are not sequences.Perhaps in future we will add a per-field boolean option for using identity check.
sobolevn commentedon Dec 30, 2024
I think that in this case (no revert) we need to update the docs to reflect the behaviour change. The OP has several examples of older wordings like:
daskol commentedon Dec 30, 2024
Indeed, dataclasses usually are not sequences but they are fancy sequences. At least, it was design goal according to PEP-557. Anyway, I'd like to hear @ericvsmith opinion on this matter since he initially drafted data classes.
Another consequence of this breaking change is compatibility with third party data class libraries (e.g. general-purpose
attrsandpydanticor domain purposechexorflax.struct).General purpose
attrs.General purpose
pydantic.From my perspective, new semantic of
__eq__is viable change in general but it requires notice period. Third parties and a lot of users appeared in six years since PEP-557 rely on the existing semantics. It will take huge community effort to bring consistency.ericvsmith commentedon Dec 30, 2024
I think it's unfortunate that there wasn't a warning of this change. But at this point it has been released, and rolling it back is problematic. Maybe a better notice in the release notes would be enough?
I don't think the PEP comment dataclasses of can be thought of as “mutable namedtuples with defaults” implies that they should be implemented with tuple semantics where possible. The statement is a very high level overview of the functionality.
@rhettinger : what are your thoughts?
daskol commentedon Jan 8, 2025
@rhettinger Any comments?
ericvsmith commentedon Sep 17, 2025
The changed behavior is being documented and tested in #138991.
Hnasar commentedon Nov 5, 2025
Actually, attrs also made a similar breaking change in 24.1.0:
So now new attrs and new dataclasses agree, at least.
However, unlike dataclasses.field, attrs.field allows you to pass per-field equality overrides which helps. But otherwise for dataclasses, what we have as a workaround is