Skip to content

Commit 55be560

Browse files
authored
Merge pull request #609 from uttam12331/fix/slots-mangled-subclass-506
Fix mangled __slots__ attribute lost for subclass instances (#506)
2 parents a644e0b + 8c3377b commit 55be560

3 files changed

Lines changed: 29 additions & 5 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
- Fixed missing type changes between equal-comparing items inside ordered iterables, e.g. `DeepDiff([2], [2.0])` now reports the `int``float` change like `DeepDiff(2, 2.0)` and `DeepDiff({'a': 2}, {'a': 2.0})` already do (issue #605).
55

66
- v9-1-0
7+
- Fixed `_dict_from_slots` dropping a parent-declared mangled `__slots__` attribute for a subclass instance, because the name was unmangled using the concrete instance type instead of the declaring class (#506)
78
- Added multiprocessing support for DeepDiff: parallel distance computation and parallel subtree diffing with aggregated worker stats, deterministic ordering, and automatic fallback to serial when unsafe (e.g. `custom_operators`, `*_obj_callback`, `ignore_order_func`)
89
- Added wildcard/glob pattern support for `exclude_paths` and `include_paths` thanks to [akshat62](https://github.com/akshat62)
910
- Reimplemented internal cache for improved performance

deepdiff/diff.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -503,10 +503,13 @@ def _skip_report_for_include_glob(self, level):
503503

504504
@staticmethod
505505
def _dict_from_slots(object: Any) -> Dict[str, Any]:
506-
def unmangle(attribute: str) -> str:
506+
def unmangle(attribute: str, klass: type) -> str:
507+
# A mangled slot (e.g. ``__id``) is stored under the name of the
508+
# class that declared it, not the concrete instance type, so a
509+
# subclass instance would otherwise lose a parent-defined slot (#506).
507510
if attribute.startswith('__') and attribute != '__weakref__':
508511
return '_{type}{attribute}'.format(
509-
type=type(object).__name__,
512+
type=klass.__name__,
510513
attribute=attribute
511514
)
512515
return attribute
@@ -522,11 +525,15 @@ def unmangle(attribute: str) -> str:
522525
slots = getattr(type_in_mro, '__slots__', None)
523526
if slots:
524527
if isinstance(slots, strings):
525-
all_slots.append(slots)
528+
all_slots.append((type_in_mro, slots))
526529
else:
527-
all_slots.extend(slots)
530+
all_slots.extend((type_in_mro, i) for i in slots)
528531

529-
return {i: getattr(object, key) for i in all_slots if hasattr(object, key := unmangle(i))}
532+
return {
533+
i: getattr(object, key)
534+
for (klass, i) in all_slots
535+
if hasattr(object, key := unmangle(i, klass))
536+
}
530537

531538
def _diff_enum(self, level: Any, parents_ids: FrozenSet[int]=frozenset(), local_tree: Optional[Any]=None) -> None:
532539
t1 = detailed__dict__(level.t1, include_keys=ENUM_INCLUDE_KEYS)

tests/test_diff_text.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,22 @@ class ClassB(ClassA):
868868
result = {}
869869
assert result == ddiff
870870

871+
def test_dict_from_slots_of_subclass_with_mangled_attribute(self):
872+
# A mangled slot (e.g. ``__id``) declared on a parent class is stored
873+
# under the parent's mangled name, so it must be unmangled using the
874+
# declaring class, not the concrete instance type. Otherwise a subclass
875+
# instance silently loses the parent-defined slot. See #506.
876+
class ClassA:
877+
__slots__ = ('__id',)
878+
879+
def __init__(self, id):
880+
self.__id = id
881+
882+
class ClassB(ClassA):
883+
pass
884+
885+
assert DeepDiff._dict_from_slots(ClassA(5)) == {'__id': 5}
886+
assert DeepDiff._dict_from_slots(ClassB(5)) == {'__id': 5}
871887

872888
def test_custom_class_changes_none_when_ignore_type(self):
873889
ddiff1 = DeepDiff({'a': None}, {'a': 1}, ignore_type_subclasses=True, ignore_type_in_groups=[(int, float)])

0 commit comments

Comments
 (0)