@@ -180,7 +180,58 @@ def _get_wrapped_exception(exc: BaseException) -> Optional[BaseException]: # no
180180def comparator (orig : Any , new : Any , superset_obj : bool = False ) -> bool :
181181 """Compare two objects for equality recursively. If superset_obj is True, the new object is allowed to have more keys than the original object. However, the existing keys/values must be equivalent."""
182182 try :
183- # Handle exceptions specially - before type check to allow wrapper comparison
183+ orig_type = type (orig )
184+ new_type = type (new )
185+
186+ # Fast-path: type identity checks for the most common return-value types.
187+ # `orig_type is T` is a single pointer comparison — cheaper than frozenset hash
188+ # lookup or isinstance MRO traversal — and these 4 types dominate real workloads.
189+ if orig_type is new_type :
190+ if orig_type is str :
191+ if orig == new :
192+ return True
193+ if _is_temp_path (orig ) and _is_temp_path (new ):
194+ return _normalize_temp_path (orig ) == _normalize_temp_path (new )
195+ return False
196+ if orig_type is list or orig_type is tuple :
197+ if len (orig ) != len (new ):
198+ return False
199+ if orig :
200+ all_fast_scalars = True
201+ for idx , elem1 in enumerate (orig ):
202+ elem_type = type (elem1 )
203+ if elem_type is not type (new [idx ]) or elem_type not in _FAST_SEQUENCE_EQ_TYPES :
204+ all_fast_scalars = False
205+ break
206+ if all_fast_scalars and orig == new :
207+ return True
208+
209+ for idx , elem1 in enumerate (orig ):
210+ if not comparator (elem1 , new [idx ], superset_obj ):
211+ return False
212+ return True
213+ if orig_type is dict :
214+ if superset_obj :
215+ return all (k in new and comparator (v , new [k ], superset_obj ) for k , v in orig .items ())
216+ if len (orig ) != len (new ):
217+ return False
218+ for key in orig :
219+ if key not in new :
220+ return False
221+ if not comparator (orig [key ], new [key ], superset_obj ):
222+ return False
223+ return True
224+ if orig_type is float :
225+ if orig == new :
226+ return True
227+ if math .isnan (orig ) and math .isnan (new ):
228+ return True
229+ return math .isclose (orig , new )
230+ # O(1) frozenset lookup for remaining common types (int, bool, None, Decimal, etc.)
231+ if orig_type in _IDENTITY_EQ_TYPES :
232+ return orig == new
233+
234+ # Handle exceptions specially - before the generic type-mismatch path to allow wrapper comparison
184235 if isinstance (orig , BaseException ) and isinstance (new , BaseException ):
185236 if isinstance (orig , PicklePlaceholderAccessError ) or isinstance (new , PicklePlaceholderAccessError ):
186237 # If this error was raised, there was an attempt to access the PicklePlaceholder, which represents an unpickleable object.
@@ -189,7 +240,7 @@ def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool:
189240 return False
190241
191242 # If types match exactly, compare attributes
192- if type ( orig ) is type ( new ) :
243+ if orig_type is new_type :
193244 orig_dict = {k : v for k , v in orig .__dict__ .items () if not k .startswith ("_" )}
194245 new_dict = {k : v for k , v in new .__dict__ .items () if not k .startswith ("_" )}
195246 return comparator (orig_dict , new_dict , superset_obj )
@@ -207,59 +258,11 @@ def comparator(orig: Any, new: Any, superset_obj: bool = False) -> bool:
207258
208259 return False
209260
210- orig_type = type (orig )
211- if orig_type is not type (new ):
261+ if orig_type is not new_type :
212262 # distinct type objects are created at runtime, even if the class code is exactly the same, so we can only compare the names
213- if orig_type .__name__ != type ( new ) .__name__ or orig_type .__qualname__ != type ( new ) .__qualname__ :
263+ if orig_type .__name__ != new_type .__name__ or orig_type .__qualname__ != new_type .__qualname__ :
214264 return False
215265
216- # Fast-path: type identity checks for the most common return-value types.
217- # `orig_type is T` is a single pointer comparison — cheaper than frozenset hash
218- # lookup or isinstance MRO traversal — and these 4 types dominate real workloads.
219- if orig_type is str :
220- if orig == new :
221- return True
222- if _is_temp_path (orig ) and _is_temp_path (new ):
223- return _normalize_temp_path (orig ) == _normalize_temp_path (new )
224- return False
225- if orig_type is list or orig_type is tuple :
226- if len (orig ) != len (new ):
227- return False
228- if orig :
229- all_fast_scalars = True
230- for idx , elem1 in enumerate (orig ):
231- elem_type = type (elem1 )
232- if elem_type is not type (new [idx ]) or elem_type not in _FAST_SEQUENCE_EQ_TYPES :
233- all_fast_scalars = False
234- break
235- if all_fast_scalars and orig == new :
236- return True
237-
238- for idx , elem1 in enumerate (orig ):
239- if not comparator (elem1 , new [idx ], superset_obj ):
240- return False
241- return True
242- if orig_type is dict :
243- if superset_obj :
244- return all (k in new and comparator (v , new [k ], superset_obj ) for k , v in orig .items ())
245- if len (orig ) != len (new ):
246- return False
247- for key in orig :
248- if key not in new :
249- return False
250- if not comparator (orig [key ], new [key ], superset_obj ):
251- return False
252- return True
253- if orig_type is float :
254- if orig == new :
255- return True
256- if math .isnan (orig ) and math .isnan (new ):
257- return True
258- return math .isclose (orig , new )
259- # O(1) frozenset lookup for remaining common types (int, bool, None, Decimal, etc.)
260- if orig_type in _IDENTITY_EQ_TYPES :
261- return orig == new
262-
263266 # Slower isinstance path for subclasses (deque, ChainMap, etc.)
264267 if isinstance (orig , (list , tuple , deque , ChainMap )):
265268 if len (orig ) != len (new ):
0 commit comments