Skip to content

Commit 849271b

Browse files
committed
[BugFix] Preserve TensorDict fields in from_dataclass
1 parent eabe90f commit 849271b

2 files changed

Lines changed: 26 additions & 10 deletions

File tree

tensordict/tensorclass.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,14 @@ class is created. Without effect if :attr:`obj` is a type.
810810
clz._tensor_only = tensor_only
811811
else:
812812
clz = dest_cls
813-
result = clz(**asdict(obj), batch_size=batch_size, device=device)
813+
data = asdict(obj)
814+
if clz._tensor_only:
815+
# ``asdict`` recursively deep-copies values, which would discard the
816+
# identity and lock state of TensorDicts. Keep TensorDict-annotated
817+
# fields intact and let the tensor-only constructor normalize mappings.
818+
for key in clz._tensordict_fields:
819+
data[key] = getattr(obj, key)
820+
result = clz(**data, batch_size=batch_size, device=device)
814821
if auto_batch_size:
815822
if batch_size is not None:
816823
raise TypeError(
@@ -1656,9 +1663,7 @@ def _is_tensordict_annotation(type_hint: Any) -> bool:
16561663
return isinstance(type_hint, type) and issubclass(type_hint, TensorDictBase)
16571664

16581665

1659-
def _set_tensorclass_type_hints(
1660-
cls: type, type_hints: dict[str, Any]
1661-
) -> None:
1666+
def _set_tensorclass_type_hints(cls: type, type_hints: dict[str, Any]) -> None:
16621667
"""Store resolved hints and cache fields with TensorDict-like annotations."""
16631668
cls._tensordict_fields = frozenset(
16641669
key
@@ -1675,9 +1680,7 @@ def _normalize_nested_mapping(
16751680
if isinstance(mapping, TensorDictBase):
16761681
return mapping
16771682
return {
1678-
key: _normalize_nested_mapping(value)
1679-
if isinstance(value, Mapping)
1680-
else value
1683+
key: _normalize_nested_mapping(value) if isinstance(value, Mapping) else value
16811684
for key, value in mapping.items()
16821685
}
16831686

@@ -2048,9 +2051,7 @@ def _setattr_tensor_only(self, key: str, value: Any) -> None: # noqa: D417
20482051
if value is None:
20492052
self._non_tensordict[key] = None
20502053
return
2051-
value, _ = _convert_mapping_for_field(
2052-
key, value, type(self)._tensordict_fields
2053-
)
2054+
value, _ = _convert_mapping_for_field(key, value, type(self)._tensordict_fields)
20542055
out = self._set_str(key, value, inplace=False, validated=False, ignore_lock=False)
20552056
if out is not self:
20562057
raise RuntimeError(

test/tensorclass/test_tensorclass.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3663,6 +3663,21 @@ class Data:
36633663
assert isinstance(tc.data, TensorDict)
36643664
assert tc.data["tensor"] == 1
36653665

3666+
@pytest.mark.parametrize("nested", [False, True])
3667+
def test_tensor_only_from_dataclass_preserves_tensordict(self, nested):
3668+
@dataclasses.dataclass
3669+
class Data:
3670+
data: TensorDict
3671+
3672+
value = TensorDict({"tensor": torch.ones(())}).lock_()
3673+
data = UserDict({"nested": value}) if nested else value
3674+
3675+
tc = from_dataclass(Data(data=data), tensor_only=True)
3676+
result = tc.data["nested"] if nested else tc.data
3677+
3678+
assert result is value
3679+
assert result.is_locked
3680+
36663681
def test_mapping_with_any_annotation_stays_non_tensor(self):
36673682
class NonTensorMapping(TensorClass):
36683683
data: Any

0 commit comments

Comments
 (0)