[New-bugs-announce] [issue47073] Solution for recursion error when comparing dataclass objects

Yonatan Dankner report at bugs.python.org
Sun Mar 20 18:56:45 EDT 2022


New submission from Yonatan Dankner <yonatan.dankner at gmail.com>:

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!

----------
components: Library (Lib)
messages: 415641
nosy: dankner
priority: normal
severity: normal
status: open
title: Solution for recursion error when comparing dataclass objects
type: behavior
versions: Python 3.11

_______________________________________
Python tracker <report at bugs.python.org>
<https://bugs.python.org/issue47073>
_______________________________________


More information about the New-bugs-announce mailing list