@@ -230,14 +230,17 @@ def _flattened(node: Any, through: frozenset[str], depth: int = 0) -> Any:
230230 type (node ),
231231 {name : _flattened (value , through , deeper ) for name , value in model_field_values (node ).items ()},
232232 )
233- if isinstance (node , tuple ) and hasattr (node , "_fields" ):
234- return type (node )(* [_flattened (item , through , deeper ) for item in node ])
235- if isinstance (node , (list , tuple )):
236- return type (node )(_flattened (item , through , deeper ) for item in node )
237- if isinstance (node , dict ):
233+ # what a value is, is asked of its class from here on: a value may answer `isinstance` itself
234+ held : Any = node
235+ kind = type (held )
236+ if issubclass (kind , tuple ) and hasattr (held , "_fields" ):
237+ return kind (* [_flattened (item , through , deeper ) for item in held ])
238+ if issubclass (kind , (list , tuple )):
239+ return kind (_flattened (item , through , deeper ) for item in held )
240+ if issubclass (kind , dict ):
238241 # a key stays as held: taken apart, a record used as one could not be hashed
239- rebuilt = {key : _flattened (value , through , deeper ) for key , value in node .items ()}
240- return rebuilt if type ( node ) is dict else _as_its_class (node , rebuilt )
242+ rebuilt = {key : _flattened (value , through , deeper ) for key , value in held .items ()}
243+ return rebuilt if kind is dict else _as_its_class (held , rebuilt )
241244 return node
242245
243246
@@ -293,19 +296,19 @@ def _mend(memo: dict[int, Any]) -> None:
293296 nothing, so that ends. A field attrs compares through a key is held raw, and stays so.
294297 """
295298 for rebuilt in list (memo .values ()):
296- if isinstance ( rebuilt , dict ):
299+ if issubclass ( type ( rebuilt ) , dict ):
297300 raw = rebuilt .compared_by if isinstance (rebuilt , TakenApart ) else ()
298301 for key , value in rebuilt .items ():
299302 if key not in raw :
300303 rebuilt [key ] = _mended (value , memo )
301- elif isinstance ( rebuilt , list ):
304+ elif issubclass ( type ( rebuilt ) , list ):
302305 rebuilt [:] = [_mended (item , memo ) for item in rebuilt ]
303306
304307
305308def _mended (value : Any , memo : dict [int , Any ]) -> Any :
306309 """*value* as it was rebuilt where it was left as it was, or made again where it is a tuple holding such a one."""
307310 rebuilt = memo .get (id (value ), value )
308- if rebuilt is not value or not isinstance ( value , tuple ):
311+ if rebuilt is not value or not issubclass ( type ( value ) , tuple ):
309312 return rebuilt
310313 items = [_mended (item , memo ) for item in value ]
311314 if all (new is old for new , old in zip (items , value , strict = True )):
@@ -343,11 +346,11 @@ def _flattened_once(node: Any, through: frozenset[str], memo: dict[int, Any]) ->
343346 for name , value in fields .items ():
344347 taken [name ] = value if name in taken .compared_by else _flattened_once (value , through , memo )
345348 return taken
346- if not isinstance ( node , (list , tuple , dict )):
349+ if not issubclass ( type ( node ) , (list , tuple , dict )):
347350 return node
348351 # no generator: through one a level was three frames on Python 3.10, 331 levels deep against 496
349352 rebuilt : Any
350- if isinstance ( node , dict ):
353+ if issubclass ( type ( node ) , dict ):
351354 rebuilt = memo [id (node )] = {} if type (node ) is dict else _of_its_class (node )
352355 put = _put_into (rebuilt )
353356 for key , value in node .items ():
@@ -400,19 +403,30 @@ def _read_through(obj: object) -> frozenset[str] | None:
400403 return None
401404
402405
403- def as_fields (value : object , beside : object = None ) -> dict | None :
404- """A plain dict as itself , anything else through `comparable_fields`.
406+ def as_fields (value : object , beside : object = None ) -> Any :
407+ """A dict or another mapping by its keys , anything else through `comparable_fields`.
405408
406409 A dict *beside* a record taken apart all the way down is taken apart as deep. Left as held, a record the
407410 dict holds was compared whole against the fields of the equal one the record holds, and the two differed.
411+ A dict that is a record as well stays as held: taken apart it was read by its fields, and a key it held
412+ beside them was gone.
413+
414+ What is a dict is asked of the class, since a value may answer `isinstance` itself. A mapping that is no
415+ dict and no record is its keys too: refused, a `MappingProxyType` beside a record was called not dict-like,
416+ and a `ChainMap` was read through the attribute it keeps its maps in.
408417 """
409- if not isinstance (value , dict ):
410- return comparable_fields (value )
411- through = None if isinstance (beside , dict ) else _read_through (beside )
412- return value if through is None else cast ("dict" , _fields_through (value , through ))
418+ if not issubclass (type (value ), dict ):
419+ own = _read_through (value )
420+ if own is not None :
421+ return _fields_through (value , own )
422+ return value if mapping_shaped (value , check_values = False ) else comparable_fields (value )
423+ through = None if issubclass (type (beside ), dict ) else _read_through (beside )
424+ if through is None or _read_through (value ) is not None :
425+ return value
426+ return _fields_through (value , through )
413427
414428
415- def fields_pair (value : object , other : object ) -> tuple [dict | None , dict | None ]:
429+ def fields_pair (value : object , other : object ) -> tuple [Any , Any ]:
416430 """Two values through their fields, each read beside the other (`as_fields`)."""
417431 return as_fields (value , other ), as_fields (other , value )
418432
@@ -817,7 +831,7 @@ def _nested_differs(
817831
818832def _keyed (value : object , beside : object ) -> Any :
819833 """A mapping as what a key path reads it through, its keys, and anything else through its fields."""
820- if mapping_shaped (value , check_values = False ) and not isinstance ( value , dict ):
834+ if mapping_shaped (value , check_values = False ) and not issubclass ( type ( value ) , dict ):
821835 return value
822836 return as_fields (value , beside )
823837
0 commit comments