TL;DR: A possible method to avoid infinite recursion when comparing dataclasses with circular references, using a decorator.
>>> @dataclass
... class A:
... x: int = 1
... self_reference: Any = field(init=False)
...
... def __post_init__(self):
... self.self_reference = self
...
>>> A(x=1) == A(x=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 3, in __eq__
File "<string>", line 3, in __eq__
File "<string>", line 3, in __eq__
[Previous line repeated 330 more times]
RecursionError: maximum recursion depth exceeded in comparison
It should in my opinion return True.
To avoid a recursion error, I would like to suggest a solution using a wrapper, similar to the one implemented for repr (see dataclasses._recursive_repr):
If within the process of comparing two objects, we have encountered an attempt to compare these objects (meaning we have reached a loop), we should return true, since no inequalities will be discovered if we continue recursing.
I suggest an addition to dataclasses.py which would look something like this (drew inspiration from _recursive_repr):
def _recursive_eq(user_function):
# Decorator to make a eq function return True when a loop was detected.
eq_running = set()
@functools.wraps(user_function)
def wrapper(self, other):
key = id(self), id(other), _thread.get_ident()
if key in eq_running:
return True
eq_running.add(key)
try:
result = user_function(self, other)
finally:
eq_running.discard(key)
return result
return wrapper
# And then replacing the _cmp_fn with the following.
def _cmp_fn(name, op, self_tuple, other_tuple):
return _recursive_eq(_create_fn(name,
('self', 'other'),
[ 'if other.__class__ is self.__class__:',
f' return {self_tuple}{op}{other_tuple}',
'return NotImplemented']))
I would like to hear your thoughts!
|