Skip to content

Instantly share code, notes, and snippets.

@sdshlanta
Created April 13, 2023 17:28
Show Gist options
  • Save sdshlanta/ccafe487a32561d2c33b175b9a085de4 to your computer and use it in GitHub Desktop.
Save sdshlanta/ccafe487a32561d2c33b175b9a085de4 to your computer and use it in GitHub Desktop.
This is a utility class for safely checking dataclass structures when they may contain circular references. Such as in the case of parent child relationships where parents have references to their children and the children have references to all of their parents.
from dataclasses import (
dataclass,
fields,
)
@dataclass(eq=False)
class RecursiveCompareMixin:
"""A utility class to provide recursion safe dataclass comparison.
"""
def __eq__(self, __o: object) -> bool:
# Check if we are are already checking, if we are, return True.
# Returning True here is fine because the only case where we recurse
# into the same object when checking equality is when examining an
# objects parent list. As we are already checking the object's
# equality, the actual comparison value will be established higher up
# the stack.
if getattr(self, '__already_checking__', False):
return True
# Set the recursion guard to prevent infinite recursion.
object.__setattr__(self, '__already_checking__', True)
# Get our fields and the other object's
s_fields = fields(self)
o_fields = fields(__o)
# If the fields are different we are not the same.
if s_fields != o_fields:
return False
# Compare the fields of each object. If there is a difference, return
# False.
for s_field, o_field in zip(s_fields, o_fields):
if getattr(self, s_field.name) != getattr(__o, o_field.name):
return False
# After finishing the comparison set the recursion guard to False to
# allow for the object to be checked again.
object.__setattr__(self, '__already_checking__', False)
return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment