Add set_freezable() for per-object freeze policy#77
Merged
mjp41 merged 8 commits intoimmutable-mainfrom Mar 6, 2026
Merged
Conversation
Add set_freezable(obj, status) to control whether individual objects
can be frozen. The status is one of four values:
FREEZABLE_YES - always freezable
FREEZABLE_NO - never freezable
FREEZABLE_EXPLICIT - freezable only when freeze() is called directly
on the object, not when reached as a child
FREEZABLE_PROXY - reserved for future module proxy use
Storage uses a two-tier strategy: first tries setting a __freezable__
attribute on the object (works for any object with a __dict__), falling
back to a weakref-keyed dictionary in the interpreter state (works for
__slots__ objects with __weakref__ support). If neither is possible,
raises TypeError.
During freeze, check_freezable() queries the status by checking the
__freezable__ attribute first, then the weakref dictionary. For
EXPLICIT, the freeze root object is tracked in FreezeState so that
direct vs child freezing can be distinguished.
The old standalone __freezable__ = False check in traverse_freeze is
removed; all __freezable__ handling is now unified through
check_freezable() and _PyImmutability_GetFreezable().
Changes:
- Include/internal/pycore_immutability.h: add _Py_freezable_status
enum, freezable_objects dict, and destroy_objects_cb to state
- Include/cpython/immutability.h: declare SetFreezable/GetFreezable
- Python/immutability.c: implement SetFreezable (attr-first, weakref
fallback), GetFreezable (attr-first, weakref fallback),
_destroy_dict callback, FreezeState.root, updated check_freezable
- Modules/_immutablemodule.c: expose set_freezable() and constants
- Lib/immutable.py: export set_freezable and FREEZABLE_* constants
- Python/pystate.c: cleanup new state fields in interpreter_clear()
- Lib/test/test_freeze/test_set_freezable.py: 18 tests covering all
status values, storage strategies, edge cases, and constants
xFrednet
reviewed
Mar 6, 2026
Collaborator
xFrednet
left a comment
There was a problem hiding this comment.
Some high level nits, but the rest LGTM. Nice work :D
| def test_object_without_weakref_support_raises(self): | ||
| # Built-in ints don't support weak references or attribute setting. | ||
| with self.assertRaises(TypeError): | ||
| set_freezable(42, FREEZABLE_NO) |
Collaborator
There was a problem hiding this comment.
Should we allow setting freezability for shallow immutable objects in the first place?
Collaborator
Author
There was a problem hiding this comment.
Good question, I don't know. There are a bunch of defaults I am not sure on the right choices for.
|
|
||
| // Check is object is subclass of NotFreezable | ||
| // TODO: Would be nice for this to be faster. | ||
| if (PyObject_IsInstance(obj, (PyObject *)&_PyNotFreezable_Type) == 1){ |
Collaborator
There was a problem hiding this comment.
Now we should be able to remove the _PyNotFreezable_Type (Can be done in a follow up.)
Co-authored-by: Fridtjof Stoldt <xFrednet@gmail.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.
Add
set_freezable(obj, status)to control whether individual objectscan be frozen. The status is one of four values:
FREEZABLE_YES- always freezableFREEZABLE_NO- never freezableFREEZABLE_EXPLICIT- freezable only when freeze() is called directlyon the object, not when reached as a child
FREEZABLE_PROXY- reserved for future module proxy useStorage uses a two-tier strategy: first tries setting a
__freezable__attribute on the object (works for any object with a
__dict__), fallingback to a weakref-keyed dictionary in the interpreter state (works for
__slots__objects with__weakref__support). If neither is possible,raises TypeError.
During freeze,
check_freezable()queries the status by checking the__freezable__attribute first, then the weakref dictionary. ForEXPLICIT, the freeze root object is tracked in
FreezeStateso thatdirect vs child freezing can be distinguished.
The old standalone
__freezable__ = Falsecheck intraverse_freezeisremoved; all
__freezable__handling is now unified throughcheck_freezable()and_PyImmutability_GetFreezable().Changes:
Include/internal/pycore_immutability.h: add_Py_freezable_statusenum,
freezable_objectsdict, anddestroy_objects_cbto stateInclude/cpython/immutability.h: declareSetFreezable/GetFreezablePython/immutability.c: implementSetFreezable(attr-first, weakreffallback),
GetFreezable(attr-first, weakref fallback),_destroy_dictcallback,FreezeState.root, updatedcheck_freezableModules/_immutablemodule.c: exposeset_freezable()and constantsLib/immutable.py: exportset_freezableandFREEZABLE_*constantsPython/pystate.c: cleanup new state fields ininterpreter_clear()Lib/test/test_freeze/test_set_freezable.py: 18 tests covering allstatus values, storage strategies, edge cases, and constants
_blocking_onand_module_locksto use this mechanism.