Skip to content

Latest commit

 

History

History
238 lines (179 loc) · 7.2 KB

README.md

File metadata and controls

238 lines (179 loc) · 7.2 KB

Deque subclass pickle state (#4769)

Original issue. Resolved in the reported scope. The original regression test passes. Protocols 0–5 preserve dict/slots state, type, contents and maxlen in 36 subclass round trips.

Environment

  • Source and standard library: f39b054b9c8c.
  • Verified October 5, 2026 on macOS 26.5.2 ARM64.
  • RustPython: 0.6.1, Python 3.14.0.alpha; native ARM64.
  • Comparison: CPython 3.14.6 ARM64.

Use absolute paths for the variables below:

  • RP: the RustPython executable for this commit.
  • CP: the CPython 3.14.6 executable.
  • SRC: the source directory at this commit, including its matching Lib.
  • CASE: the directory containing the files shown below.

Shell variables replace recorded absolute paths. Export them so the Python inputs can use them:

export RP CP SRC CASE
unset PYTHONPATH PYTHONHOME PYTHONWARNINGS PYTHONSTARTUP
export PYTHONDONTWRITEBYTECODE=1
export LANG=C LC_ALL=C NO_COLOR=1 TERM=dumb

Reproducer

Save as probes.py. This contains the selected function plus the original imports and dispatcher; unrelated issue functions are omitted.

import sys, json


def issue4769():
    import pickle, copy
    from collections import deque

    global Deque, DequeWithSlots

    class Deque(deque):
        pass

    class DequeWithSlots(deque):
        __slots__ = ("slot", "__dict__")

    # Names must be globally resolvable for pickle.
    Deque.__qualname__ = "Deque"
    DequeWithSlots.__qualname__ = "DequeWithSlots"
    literal = pickle.loads(pickle.dumps(pickle.HIGHEST_PROTOCOL))
    try:
        literal.__dict__
    except AttributeError:
        print("literal original: integer has no __dict__")
    total = 0
    for cls in (Deque, DequeWithSlots):
        for values, maxlen in (
            ([], None),
            (["a", "b", "c"], None),
            (["a", "b", "c"], 2),
        ):
            d = cls(values, maxlen=maxlen)
            d.x = "value"
            d.extra = ["list state"]
            if cls is DequeWithSlots:
                d.slot = ["slot state"]
            for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
                e = pickle.loads(pickle.dumps(d, protocol))
                assert type(e) is cls and e is not d
                assert list(e) == list(d) and e.maxlen == d.maxlen
                assert e.__dict__ == d.__dict__
                if cls is DequeWithSlots:
                    assert e.slot == d.slot
                total += 1
    print("pickle protocols:", list(range(pickle.HIGHEST_PROTOCOL + 1)))
    print("subclass roundtrips:", total)


globals()["issue" + sys.argv[1]]()

Run

cd "$SRC"

RustPython:

RUSTPYTHONPATH="$SRC/Lib" "$RP" -B "$CASE/probes.py" 4769

CPython:

"$CP" -B "$CASE/probes.py" 4769

Results

Expected: Pickling a deque subclass must preserve its instance state.

RustPython and CPython 3.14.6

Exit code: 0. No timeout.

stdout:

literal original: integer has no __dict__
pickle protocols: [0, 1, 2, 3, 4, 5]
subclass roundtrips: 36

stderr:

No output.

Historical failure

Previously recorded at 25ca331dc81e; exit code 1. This run was not repeated alongside the current results. Historical inputs may differ from the expanded checks above.

stderr:

======================================================================
ERROR: test_copy_pickle (test.test_deque.TestSubclass.test_copy_pickle)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "$SRC/pylib/Lib/test/test_deque.py", line 839, in test_copy_pickle
    self.assertEqual(e.x, d.x)
AttributeError: 'Deque' object has no attribute 'x'

----------------------------------------------------------------------
Ran 1 test in 0.007s

FAILED (errors=1)

Historical output excerpt; runner metadata and repeated shutdown warnings are omitted.

Existing regression tests

Save the following runner as unittest-probe.py. It reports skip and expected-failure markers and runs the repository tests unchanged.

import unittest, json, sys

suite = unittest.defaultTestLoader.loadTestsFromNames(sys.argv[1:])


def flatten(item):
    if isinstance(item, unittest.TestSuite):
        return [t for child in item for t in flatten(child)]
    return [item]


tests = flatten(suite)
print(
    json.dumps(
        {
            "tests": [
                {
                    "id": t.id(),
                    "skip": bool(
                        getattr(t, "__unittest_skip__", False)
                        or getattr(
                            getattr(t, t._testMethodName), "__unittest_skip__", False
                        )
                    ),
                    "expected_failure": bool(
                        getattr(
                            getattr(t, t._testMethodName),
                            "__unittest_expecting_failure__",
                            False,
                        )
                    ),
                }
                for t in tests
            ]
        },
        sort_keys=True,
    ),
    flush=True,
)
result = unittest.TextTestRunner(verbosity=2).run(suite)
print(
    json.dumps(
        {
            "run": result.testsRun,
            "skips": [(t.id(), reason) for t, reason in result.skipped],
            "expected_failures": [
                (t.id(), trace) for t, trace in result.expectedFailures
            ],
            "unexpected_successes": [t.id() for t in result.unexpectedSuccesses],
            "success": result.wasSuccessful(),
        },
        sort_keys=True,
    )
)
sys.exit(0 if result.wasSuccessful() else 1)
RUSTPYTHONPATH="$SRC/Lib" "$RP" -B "$CASE/unittest-probe.py" \
  test.test_deque.TestSubclass.test_copy_pickle

The recorded RustPython run executed 1 test: all passed, with no skips, expected failures or unexpected successes. Exit code: 0; no timeout. Test progress and OK were written to stderr.

Related change and scope

PR #7699: include getstate in deque reduction.

The issue snippet accidentally pickles pickle.HIGHEST_PROTOCOL itself. That literal and the corrected intended input are separate. Recursive deque pickle is outside this reported nonrecursive case.

First fixing commit: not established.

Evidence

Recorded executable SHA-256 values:

AI assistance: OpenAI Codex.