Repository navigation
🐛 fix: build the frozendict backport the way the built-in does; document subclassing - #85
Merged
nstarman merged 4 commits intoOct 16, 2026
Conversation
The backport kept its contents in a slot named `_data`. The built-in has no such attribute, so on Python 3.15 a subclass is free to use `_data` for itself; on the backport, assigning it rebound the slot and replaced the mapping's contents. galax and coordinax both declared a `_data` field on their subclasses. Name-mangle the slot to `_frozendict__data`, so `self._data` on a subclass is just another attribute, as with the built-in. Pickles are unaffected: they rebuild through `__new__` from the data, not from slot state. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The built-in fills itself in C, so a subclass that overrides `__setattr__` constructs normally: a frozen dataclass subclass works on Python 3.15. The backport assigned its storage with `self.__data = ...`, which goes through the subclass's `__setattr__` and raised `FrozenInstanceError` there instead. Assign it with `object.__setattr__`. This also makes an `equinox.Module` subclass behave the same on every Python: its `__new__` drops the arguments, so it is an empty mapping, as on 3.15, rather than failing only on older Pythons. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Since 0.3.0 `ImmutableMap` is a `frozendict`, which takes its contents in
`__new__`. Say so in a README section on subclassing: override `__new__` to
transform input, register the subclass as a PyTree node, and don't combine
it with an `equinox.Module` or a dataclass, which galax and coordinax both
did (via `ImmutableMap.__init__` and the private `_data`, now gone).
The `tree_unflatten` doctest passed `tree_flatten`'s `(children, keys)` as
`(aux_data, children)`, and documented the swapped result
`ImmutableMap({1: 'a', 2: 'b'})`; the `jax.tree.unflatten` example passed
that pair as the leaves. Both now round-trip `d`.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Pylint's protected-access fires on `other.__data` now that the slot is name-mangled; `other` is a `frozendict` too. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #85 +/- ##
========================================
Coverage ? 100.00%
========================================
Files ? 3
Lines ? 98
Branches ? 0
========================================
Hits ? 98
Misses ? 0
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
nstarman
added a commit
that referenced
this pull request
Oct 16, 2026
…lt-in does; document subclassing (#86) Co-authored-by: Nathaniel Starkman <nstarman@users.noreply.github.com>
nstarman
added a commit
to nstarman/galax
that referenced
this pull request
Oct 16, 2026
0.3.1 builds its pre-3.15 `frozendict` backport the way the built-in does (GalacticDynamics/xmmutablemap#85), so galax now behaves the same on every supported Python. The lock already had 0.3.1; only the specifiers move. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nstarman
added a commit
to nstarman/galax
that referenced
this pull request
Oct 16, 2026
The release with GalacticDynamics/xmmutablemap#85. galax no longer depends on how `ImmutableMap` stores its contents, so this is a floor on the fixed release rather than a functional need. The lock already had 0.3.1; only the specifier moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
xmmutablemap 0.3.0 broke both known subclassers, galax (
CompositePotential,AbstractCompositePhaseSpaceCoordinate) and coordinax (KinematicSpace). Each is anequinox.Modulethat also subclassedImmutableMap, calledImmutableMap.__init__, and declared the private_dataas a field. That can't work onceImmutableMapis afrozendict, which gets its contents in__new__, and they are being fixed downstream (GalacticDynamics/coordinax#1047, GalacticDynamics/coordinax#1048). Looking into it turned up two places where the backport doesn't behave like the 3.15 built-in, plus a missing doc.Commits
_data→_frozendict__data). The built-in has no_data, so a subclass is free to set its own; on the backport that rebound the slot and replaced the mapping's contents. Pickles are unaffected: they rebuild through__new__from the data.object.__setattr__. The built-in fills itself in C, so a subclass's__setattr__never runs during construction: a frozen-dataclass subclass constructs on 3.15 but raisedFrozenInstanceErroron the backport. As a result, anequinox.Modulesubclass now behaves the same on every Python: an empty mapping, as on 3.15, instead of failing only on older Pythons.__new__to transform input; register the subclass as a PyTree node (subclasses are opaque leaves otherwise); and a warning against combining it with anequinox.Moduleor a dataclass, saying what to do instead. Also fixes thetree_unflattendoctest, which passedtree_flatten's(children, keys)in swapped order and documented the resultingImmutableMap({1: 'a', 2: 'b'}).Testing
Each of the two fixes adds a test that fails on the backport and passes on the built-in. The full suite passes on 3.10, 3.12, 3.14 and 3.15.
🤖 Generated with Claude Code