Fix numpy view py/id desync when base is shared (#449) - #612
Merged
Theelx merged 2 commits intoJul 21, 2026
Conversation
Contributor
|
Thank you for this! Sorry for not getting to it earlier. For the AI footer in the template, it was meant to be added to the commit itself. After you amend the commit to include that, I'd be happy to merge! |
Theelx
approved these changes
Jul 21, 2026
Theelx
left a comment
Contributor
There was a problem hiding this comment.
Looks good once the comment about commit footers is addressed!
Contributor
|
Oh also, I forgot this initially, but could you remove the backticks from the values inside the docstrings? We don't use markdown when rendering the docstrings so they're an unnecessary remnant of the AI that just clogs up the character count. |
Round-tripping a non-contiguous numpy view whose base is shared with
another view could raise:
ValueError: strides is incompatible with shape of requested array
and size of buffer
When a view is stored by reference to a base array,
NumpyNDArrayHandlerView also emits a decorative "values" entry "for
human-readability". Restore ignores that entry entirely when a base is
present, but flattening it went through self.context.flatten(), which
registered an extra py/id reference that the restore step never mirrored.
This desynchronized the encode/decode reference counters, so a later view
stored as a py/id reference to a shared base resolved to the wrong object
(a 0-dim array wrapping a _Proxy), producing the strides error.
Emit the decorative values without allocating a py/id slot by snapshotting
and restoring the pickler's reference bookkeeping around the call, keeping
the encode and decode id counters in lockstep.
Adds a regression test and a CHANGES.rst entry.
Fixes jsonpickle#449.
Assisted-by: Claude (Anthropic)
apoorvdarshan
force-pushed
the
fix-449-numpy-view-py-id-desync
branch
from
July 21, 2026 17:14
2322a11 to
13eb183
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
CHANGES.rstfile for this change.(+611)) and (if applicable) added a link to the issue that this fixes (e.g.(#608)).pytestand run formatting withruff format.Summary
Fixes #449. Round-tripping a non-contiguous NumPy view whose base is shared with another view raised:
The maintainer confirmed an off-by-one in the encode step (a
py/idemitted as N+1 instead of N), so the base-array reference resolved to the wrong object during restore — a 0-dim array wrapping anunpickler._Proxy, as reported in the issue thread.Root cause
When a view is stored by reference to a base array,
NumpyNDArrayHandlerView.flattenalso emits a decorativevaluesentry, documented as "not used in restore since base is present, but include values for human-readability". That decoration went throughself.context.flatten(obj.tolist(), reset=False), which registers apy/idreference in the pickler's bookkeeping.Restore, however, ignores
valuesentirely when abaseis present (it rebuilds the array frombuffer/offset/strides) and therefore never registers a matching reference. The encode and decode id counters then drift apart by one for every referenced array carrying a decorativevalues. A later view stored as{"py/id": N}to a shared base then resolves to the wrong object during restore, producing the strides error.This is easy to trigger with e.g.
sklearn'scv_results_(the issue's repro): a masked array registers an unmirrored reference and thesplitN_test_scoreviews share a single base.Fix
Emit the decorative
valueswithout allocating apy/idslot, by snapshotting and restoring the pickler's reference bookkeeping (_objs/_flattened) around the call. This keeps the encode and decode id counters in lockstep while preserving the human-readablevaluesin the output. The change is confined to the numpy extension (jsonpickle/ext/numpy.py); no core code is touched.Tests / Verification
tests/numpy_test.py::test_shared_base_after_referenced_object, a minimal regression that reproduces the exactValueErroron the pristine code and passes with the fix (masked array + two non-contiguous views sharing a base).sklearncv_results_repro from the issue now round-trips correctly with all arrays equal.pytest tests/numpy_test.py tests/sklearn_test.py tests/jsonpickle_test.py— all green (221 passed).unpicklable=Falsestill round-trip correctly.ruff check,ruff format --check, andisort --profile=blackare clean;mypy --strictintroduces no new errors in the changed file.Assisted-by: Claude (Anthropic)
Disclosure: prepared with AI assistance; reviewed and verified locally.