is_equal()
Deep equality with cycle handling and fast paths.
Implementation
Semantics: - Requires exact same type (type(a) is type(b)), like your original. - dict/Mapping: keys must match; values compared deeply. - list/tuple/Sequence (not str/bytes/bytearray): order matters. - set/frozenset/Set: order doesn't matter (direct equality). - Other types fall back to ==. - No special-casing for NaN (NaN != NaN), preserving original behavior. Complexity: O(N) in the total number of elements visited across both structures. Args: obj1: First object to compare obj2: Second object to compare Returns: True if objects are deeply equal, False otherwise
Example
is_equal([1, 2, 3], [1, 2, 3])
Expected output: True
Source Code
def is_equal(obj1: Any, obj2: Any) -> bool:
if obj1 is obj2:
return True
if type(obj1) is not type(obj2):
return False
# Fast-path for common immutables
if isinstance(obj1, (int, float, str, bool, type(None), bytes, bytearray, memoryview, range)):
return obj1 == obj2
# Iterative DFS with cycle detection on (id(a), id(b)) pairs
seen: set[Tuple[int, int]] = set()
stack: List[Tuple[Any, Any]] = [(obj1, obj2)]
while stack:
a, b = stack.pop()
if a is b:
continue
if type(a) is not type(b):
return False
pid = (id(a), id(b))
if pid in seen:
continue
seen.add(pid)
# Re-check fast-path for inner elements
if isinstance(a, (int, float, str, bool, type(None), bytes, bytearray, memoryview, range)):
if a != b:
return False
continue
# Mappings: keys must match; push value pairs
if isinstance(a, Mapping):
# Quick reject on size or key set mismatch
if len(a) != len(b): # type: ignore[arg-type]
return False
# Using mapping view equality is O(n) and order-insensitive
if a.keys() != b.keys(): # type: ignore[arg-type]
return False
# Compare corresponding values
for k in a.keys():
stack.append((a[k], b[k])) # type: ignore[index]
continue
# Sets (unordered)
if isinstance(a, Set) and not isinstance(a, (str, bytes, bytearray)):
# Direct set equality is fine
if a != b: # type: ignore[comparison-overlap]
return False
continue
# Sequences (ordered) – but exclude string/bytes-like
if isinstance(a, Sequence) and not isinstance(a, (str, bytes, bytearray)):
if len(a) != len(b): # type: ignore[arg-type]
return False
# Push in order so mismatches are caught early
# (iterate by index to avoid zip generator overhead)
for i in range(len(a)): # type: ignore[arg-type]
stack.append((a[i], b[i])) # type: ignore[index]
continue
# Fallback for everything else (custom objects, etc.)
if a != b:
return False
return True