Skip to content

Commit 1d42ee5

Browse files
Preserve __dict__ and __slots__ state in deque.__reduce__ (RustPython#7699)
deque.__reduce__ passed None as the unpickle state, so a deque subclass's __dict__ and __slots__ values were dropped across a pickle round-trip. CPython's deque___reduce___impl (Modules/_collectionsmodule.c::deque___reduce___impl) calls _PyObject_GetState, which returns the dict (or a (dict, slots) tuple) so subclass attributes survive. Replace the placeholder with the result of __getstate__() on the instance. object.__getstate__ already implements the dict / dict+slots protocol, matching _PyObject_GetState.
1 parent 9794ab7 commit 1d42ee5

2 files changed

Lines changed: 5 additions & 2 deletions

File tree

Lib/test/test_deque.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,6 @@ def test_basics(self):
817817
d.clear()
818818
self.assertEqual(len(d), 0)
819819

820-
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'Deque' object has no attribute 'x'
821820
def test_copy_pickle(self):
822821
for cls in Deque, DequeWithSlots:
823822
for d in cls('abc'), cls('abcde', maxlen=4):

crates/vm/src/stdlib/_collections.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,11 @@ mod _collections {
388388
Some(v) => vm.new_pyobj((vm.ctx.empty_tuple.clone(), v)),
389389
None => vm.ctx.empty_tuple.clone().into(),
390390
};
391-
Ok(vm.new_pyobj((cls, value, vm.ctx.none(), PyDequeIterator::new(zelf))))
391+
// Use __getstate__ to capture both __dict__ and __slots__ values so
392+
// subclass attributes survive a pickle round-trip (matches CPython's
393+
// deque___reduce___impl, which calls _PyObject_GetState).
394+
let state = vm.call_method(zelf.as_object(), "__getstate__", ())?;
395+
Ok(vm.new_pyobj((cls, value, state, PyDequeIterator::new(zelf))))
392396
}
393397

394398
#[pyclassmethod]

0 commit comments

Comments
 (0)