From 6b88aa901769226f941793d3d1c99a15d2e955a1 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 14:47:55 +0100 Subject: [PATCH 1/6] Alias `isfrozen` to `is_frozen` --- Lib/immutable.py | 8 +- Lib/test/test_freeze/test_common.py | 10 +- Lib/test/test_freeze/test_core.py | 94 ++++++++-------- Lib/test/test_freeze/test_ctypes.py | 10 +- Lib/test/test_freeze/test_decorators.py | 12 +- Lib/test/test_freeze/test_implicit.py | 56 +++++----- Lib/test/test_freeze/test_module_proxy.py | 14 +-- Lib/test/test_freeze/test_multi_freeze.py | 122 ++++++++++----------- Lib/test/test_freeze/test_prefreeze.py | 38 +++---- Lib/test/test_freeze/test_reachable.py | 8 +- Lib/test/test_freeze/test_rollback.py | 48 ++++---- Lib/test/test_freeze/test_set_freezable.py | 20 ++-- Lib/test/test_freeze/test_weakref.py | 10 +- Modules/_immutablemodule.c | 11 +- Modules/clinic/_immutablemodule.c.h | 13 ++- 15 files changed, 240 insertions(+), 234 deletions(-) diff --git a/Lib/immutable.py b/Lib/immutable.py index 3d1c34e4a43c5a..a778c5acd38272 100644 --- a/Lib/immutable.py +++ b/Lib/immutable.py @@ -9,7 +9,7 @@ import _immutable as _c freeze = _c.freeze -isfrozen = _c.isfrozen +is_frozen = _c.is_frozen set_freezable = _c.set_freezable NotFreezable = getattr(_c, "NotFreezable", None) NotFreezableError = _c.NotFreezableError @@ -19,6 +19,10 @@ FREEZABLE_EXPLICIT = _c.FREEZABLE_EXPLICIT FREEZABLE_PROXY = _c.FREEZABLE_PROXY +# FIXME(immutable): For the longest time we used the name `isfrozen` +# without the underscore. This keeps the function name for now, but +# aliases it to `is_frozen` +isfrozen = is_frozen def freezable(cls): """Class decorator: mark a class as always freezable.""" @@ -47,7 +51,7 @@ def frozen(cls): __all__ = [ "freeze", - "isfrozen", + "is_frozen", "set_freezable", "NotFreezable", "NotFreezableError", diff --git a/Lib/test/test_freeze/test_common.py b/Lib/test/test_freeze/test_common.py index be12e86bcfc71d..3854f15b329cee 100644 --- a/Lib/test/test_freeze/test_common.py +++ b/Lib/test/test_freeze/test_common.py @@ -1,5 +1,5 @@ import unittest -from immutable import freeze, isfrozen, NotFreezable +from immutable import freeze, is_frozen, NotFreezable class BaseObjectTest(unittest.TestCase): @@ -14,15 +14,15 @@ def setUp(self): freeze(self.obj) def test_immutable(self): - self.assertTrue(isfrozen(self.obj)) + self.assertTrue(is_frozen(self.obj)) def test_add_attribute(self): with self.assertRaises(TypeError): self.obj.new_attribute = 'value' def test_type_immutable(self): - self.assertTrue(isfrozen(self.obj)) - self.assertTrue(isfrozen(type(self.obj)), "Type should be frozen when instance is frozen: {}".format(type(self.obj))) + self.assertTrue(is_frozen(self.obj)) + self.assertTrue(is_frozen(type(self.obj)), "Type should be frozen when instance is frozen: {}".format(type(self.obj))) class BaseNotFreezableTest(unittest.TestCase): @@ -34,7 +34,7 @@ def test_not_freezable(self): with self.assertRaises(TypeError): freeze(self.obj) - self.assertFalse(isfrozen(self.obj)) + self.assertFalse(is_frozen(self.obj)) if __name__ == '__main__': diff --git a/Lib/test/test_freeze/test_core.py b/Lib/test/test_freeze/test_core.py index 946190e458a90b..f9091e1c85c04b 100644 --- a/Lib/test/test_freeze/test_core.py +++ b/Lib/test/test_freeze/test_core.py @@ -1,5 +1,5 @@ import unittest -from immutable import freeze, NotFreezable, isfrozen +from immutable import freeze, NotFreezable, is_frozen from .test_common import BaseNotFreezableTest, BaseObjectTest @@ -19,7 +19,7 @@ def global1_inc(): class MutableGlobalTest(unittest.TestCase): # Add initial test to confirm that global_canary is mutable def test_global_mutable(self): - self.assertTrue(not isfrozen(global_canary)) + self.assertTrue(not is_frozen(global_canary)) class TestBasicObject(BaseObjectTest): @@ -34,13 +34,13 @@ class TestFloat(unittest.TestCase): def test_freeze_float(self): obj = 0.0 freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) class TestFloatType(unittest.TestCase): def test_float_type_immutable(self): obj = 0.0 c = obj.__class__ - self.assertTrue(isfrozen(c)) + self.assertTrue(is_frozen(c)) class TestList(BaseObjectTest): class C: @@ -190,25 +190,25 @@ class C: freeze(self.obj) def test_immutable(self): - self.assertTrue(isfrozen(self.obj)) - self.assertTrue(isfrozen(self.obj.a)) - self.assertTrue(isfrozen(self.obj.a.b)) - self.assertTrue(isfrozen(self.obj.d)) - self.assertTrue(isfrozen(self.obj.d[0])) - self.assertTrue(isfrozen(self.obj.d[0].e)) - self.assertTrue(isfrozen(self.obj.g)) - self.assertTrue(isfrozen(self.obj.g[1])) - self.assertTrue(isfrozen(self.obj.g[1].h)) - self.assertTrue(isfrozen(self.obj.g["two"])) - self.assertTrue(isfrozen(self.obj.g["two"].i)) + self.assertTrue(is_frozen(self.obj)) + self.assertTrue(is_frozen(self.obj.a)) + self.assertTrue(is_frozen(self.obj.a.b)) + self.assertTrue(is_frozen(self.obj.d)) + self.assertTrue(is_frozen(self.obj.d[0])) + self.assertTrue(is_frozen(self.obj.d[0].e)) + self.assertTrue(is_frozen(self.obj.g)) + self.assertTrue(is_frozen(self.obj.g[1])) + self.assertTrue(is_frozen(self.obj.g[1].h)) + self.assertTrue(is_frozen(self.obj.g["two"])) + self.assertTrue(is_frozen(self.obj.g["two"].i)) def test_set_const(self): with self.assertRaises(TypeError): self.obj.const = 1 def test_type_immutable(self): - self.assertTrue(isfrozen(type(self.obj))) - self.assertTrue(isfrozen(type(self.obj).const)) + self.assertTrue(is_frozen(type(self.obj))) + self.assertTrue(is_frozen(type(self.obj).const)) class TestFunctions(unittest.TestCase): @@ -269,8 +269,8 @@ def d(): self.assertEqual(d(), 1) freeze(d) - self.assertTrue(isfrozen(global0)) - self.assertFalse(isfrozen(global_canary)) + self.assertTrue(is_frozen(global0)) + self.assertFalse(is_frozen(global_canary)) self.assertRaises(TypeError, d) def test_hidden_global(self): @@ -292,9 +292,9 @@ def e(): return sum(test) freeze(e) - self.assertTrue(isfrozen(list)) - self.assertTrue(isfrozen(range)) - self.assertTrue(isfrozen(sum)) + self.assertTrue(is_frozen(list)) + self.assertTrue(is_frozen(range)) + self.assertTrue(is_frozen(sum)) def test_builtins_nested(self): def g(): @@ -305,18 +305,18 @@ def nested_test(): return nested_test() freeze(g) - self.assertTrue(isfrozen(list)) - self.assertTrue(isfrozen(range)) - self.assertTrue(isfrozen(sum)) + self.assertTrue(is_frozen(list)) + self.assertTrue(is_frozen(range)) + self.assertTrue(is_frozen(sum)) def test_global_fun(self): def d(): return global1_inc() freeze(d) - self.assertTrue(isfrozen(global1)) - self.assertTrue(isfrozen(global1_inc)) - self.assertFalse(isfrozen(global_canary)) + self.assertTrue(is_frozen(global1)) + self.assertTrue(is_frozen(global1_inc)) + self.assertFalse(is_frozen(global_canary)) self.assertRaises(TypeError, d) def test_globals_copy(self): @@ -348,8 +348,8 @@ def test_lambda(self): obj = TestMethods.C() obj.c = lambda x: pow(x, 2) freeze(obj) - self.assertTrue(isfrozen(TestMethods.C)) - self.assertTrue(isfrozen(pow)) + self.assertTrue(is_frozen(TestMethods.C)) + self.assertTrue(is_frozen(pow)) self.assertRaises(TypeError, obj.b, 1) self.assertEqual(obj.c(2), 4) @@ -357,9 +357,9 @@ def test_method(self): obj = TestMethods.C() freeze(obj) self.assertEqual(obj.a(), 1) - self.assertTrue(isfrozen(obj)) - self.assertTrue(isfrozen(abs)) - self.assertTrue(isfrozen(obj.val)) + self.assertTrue(is_frozen(obj)) + self.assertTrue(is_frozen(abs)) + self.assertTrue(is_frozen(obj.val)) self.assertRaises(TypeError, obj.b, 1) # Second test as the byte code can be changed by the first call self.assertRaises(TypeError, obj.b, 1) @@ -383,9 +383,9 @@ def inner(): freeze(obj) return obj, obj2, obj3 obj, obj2, obj3 = inner() - self.assertTrue(isfrozen(obj)) - self.assertTrue(isfrozen(obj2)) - self.assertFalse(isfrozen(obj3)) + self.assertTrue(is_frozen(obj)) + self.assertTrue(is_frozen(obj2)) + self.assertFalse(is_frozen(obj3)) class TestDictMutation(unittest.TestCase): class C: @@ -402,7 +402,7 @@ def set(self, x): def test_dict_mutation(self): obj = TestDictMutation.C() freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) self.assertRaises(TypeError, obj.set, 1) self.assertEqual(obj.get(), 0) @@ -412,7 +412,7 @@ def test_dict_mutation2(self): self.assertEqual(obj.get(), 1) freeze(obj) self.assertEqual(obj.get(), 1) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) self.assertRaises(TypeError, obj.set, 1) def test_dict_mutation3(self): @@ -420,7 +420,7 @@ def test_dict_mutation3(self): d = obj.__dict__ freeze(d) # Should obj be frozen? - # self.assertTrue(isfrozen(obj)) + # self.assertTrue(is_frozen(obj)) # The following line should raise an exception, as we are trying to mutate the dict with self.assertRaises(TypeError): obj.f = 1 @@ -454,9 +454,9 @@ def test_weakref(self): obj = TestWeakRef.B() c = TestWeakRef.C(obj) freeze(c) - self.assertTrue(isfrozen(c)) + self.assertTrue(is_frozen(c)) self.assertTrue(c.val() is obj) - self.assertTrue(isfrozen(c.val())) + self.assertTrue(is_frozen(c.val())) obj = None # The reference should remain as it was reachable through a frozen weakref. self.assertTrue(c.val() is not None) @@ -469,8 +469,8 @@ def test_stack_capture(self): x = {} x["frame"] = sys._getframe() freeze(x) - self.assertTrue(isfrozen(x)) - self.assertTrue(isfrozen(x["frame"])) + self.assertTrue(is_frozen(x)) + self.assertTrue(is_frozen(x["frame"])) class TestSubclass(unittest.TestCase): @@ -487,8 +487,8 @@ def b(self, val): c_obj = C(1) freeze(c_obj) - self.assertTrue(isfrozen(c_obj)) - self.assertTrue(isfrozen(C)) + self.assertTrue(is_frozen(c_obj)) + self.assertTrue(is_frozen(C)) class D(C): def __init__(self, val): super().__init__(val) @@ -585,7 +585,7 @@ def f(b=bdef): freeze(f) - self.assertTrue(isfrozen(bdef)) + self.assertTrue(is_frozen(bdef)) def test_function_kwdefaults(self): bdef = {} @@ -595,7 +595,7 @@ def f(a, **b): freeze(f) - self.assertTrue(isfrozen(bdef)) + self.assertTrue(is_frozen(bdef)) class TestNotFreezable(BaseNotFreezableTest): diff --git a/Lib/test/test_freeze/test_ctypes.py b/Lib/test/test_freeze/test_ctypes.py index 78f1cca731fcee..a7ee5090d23909 100644 --- a/Lib/test/test_freeze/test_ctypes.py +++ b/Lib/test/test_freeze/test_ctypes.py @@ -1,6 +1,6 @@ import unittest from test.support import import_helper -from immutable import isfrozen +from immutable import is_frozen from .test_common import BaseObjectTest @@ -59,8 +59,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, obj=ctypes.pointer(self.a), **kwargs) def test_contents_immutable(self): - self.assertTrue(isfrozen(self.a)) - self.assertTrue(isfrozen(TestPointer.POINT)) + self.assertTrue(is_frozen(self.a)) + self.assertTrue(is_frozen(TestPointer.POINT)) def test_set_contents(self): b = TestPointer.POINT(3, 4) @@ -80,8 +80,8 @@ def __init__(self, *args, **kwargs): super().__init__(*args, obj=TenPointsArrayType(), **kwargs) def test_point_immutable(self): - self.assertTrue(isfrozen(self.obj[0])) - self.assertTrue(isfrozen(TestArray.POINT)) + self.assertTrue(is_frozen(self.obj[0])) + self.assertTrue(is_frozen(TestArray.POINT)) def test_modify_item(self): with self.assertRaises(TypeError): diff --git a/Lib/test/test_freeze/test_decorators.py b/Lib/test/test_freeze/test_decorators.py index 54b4ebc0bbac1d..7eac96158760c2 100644 --- a/Lib/test/test_freeze/test_decorators.py +++ b/Lib/test/test_freeze/test_decorators.py @@ -2,7 +2,7 @@ import unittest from immutable import ( - freeze, isfrozen, freezable, unfreezable, explicitlyFreezable, frozen, + freeze, is_frozen, freezable, unfreezable, explicitlyFreezable, frozen, ) @@ -14,7 +14,7 @@ class C: pass obj = C() freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_freezable_returns_class(self): @freezable @@ -32,7 +32,7 @@ class C: obj = C() with self.assertRaises(TypeError): freeze(obj) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_unfreezable_returns_class(self): @unfreezable @@ -49,7 +49,7 @@ class C: pass # The class itself can be frozen when passed directly. freeze(C) - self.assertTrue(isfrozen(C)) + self.assertTrue(is_frozen(C)) def test_explicit_as_child_fails(self): @freezable @@ -80,7 +80,7 @@ def test_frozen_class_is_frozen(self): @frozen class C: pass - self.assertTrue(isfrozen(C)) + self.assertTrue(is_frozen(C)) def test_frozen_class_is_immutable(self): @frozen @@ -120,7 +120,7 @@ def test_frozen_decorator_returns_class(self): class C: pass self.assertIsInstance(C, type) - self.assertTrue(isfrozen(C)) + self.assertTrue(is_frozen(C)) if __name__ == '__main__': diff --git a/Lib/test/test_freeze/test_implicit.py b/Lib/test/test_freeze/test_implicit.py index ffebbf1d2f40a0..b710b787fe5bda 100644 --- a/Lib/test/test_freeze/test_implicit.py +++ b/Lib/test/test_freeze/test_implicit.py @@ -1,5 +1,5 @@ import unittest -from immutable import freeze, isfrozen +from immutable import freeze, is_frozen class TestImplicitImmutability(unittest.TestCase): @@ -8,136 +8,136 @@ class TestImplicitImmutability(unittest.TestCase): def test_tuple_of_immortal_ints(self): """A tuple of small ints (immortal) can be viewed as immutable.""" obj = (1, 2, 3) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_of_strings(self): """A tuple of interned strings can be viewed as immutable.""" obj = ("hello", "world") - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_of_none(self): """A tuple containing None can be viewed as immutable.""" obj = (None, None) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_nested_tuples(self): """Nested tuples of immortal objects can be viewed as immutable.""" obj = ((1, 2), (3, (4, 5))) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_with_mutable_list(self): """A tuple containing a mutable list cannot be viewed as immutable.""" obj = (1, [2, 3]) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_tuple_with_mutable_dict(self): """A tuple containing a mutable dict cannot be viewed as immutable.""" obj = (1, {"a": 2}) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_frozenset_of_ints(self): """A frozenset of ints can be viewed as immutable.""" obj = frozenset([1, 2, 3]) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_empty_tuple(self): """An empty tuple can be viewed as immutable.""" obj = () - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_empty_frozenset(self): """An empty frozenset can be viewed as immutable.""" obj = frozenset() - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_already_frozen_object(self): """An already-frozen object should return True.""" obj = [1, 2, 3] freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_containing_frozen_object(self): """A tuple containing a frozen list can be viewed as immutable.""" inner = [1, 2, 3] freeze(inner) obj = (inner, 4, 5) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_mutable_list(self): """A plain mutable list cannot be viewed as immutable.""" obj = [1, 2, 3] - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_mutable_dict(self): """A plain mutable dict cannot be viewed as immutable.""" obj = {"a": 1} - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_tuple_with_bytes(self): """A tuple containing bytes can be viewed as immutable.""" obj = (b"hello", b"world") - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_with_float(self): """A tuple with floats can be viewed as immutable.""" obj = (1.0, 2.5, 3.14) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_with_complex(self): """A tuple with complex numbers can be viewed as immutable.""" obj = (1+2j, 3+4j) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_with_bool(self): """A tuple with booleans can be viewed as immutable.""" obj = (True, False) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) - def test_isfrozen_freezes_non_immortal(self): - """A graph that can be viewed as immutable gets frozen by isfrozen.""" + def test_is_frozen_freezes_non_immortal(self): + """A graph that can be viewed as immutable gets frozen by is_frozen.""" big = 10**100 obj = (big,) - result = isfrozen(obj) + result = is_frozen(obj) self.assertTrue(result) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_of_range(self): """A tuple containing a range object can be viewed as immutable.""" obj = (range(10),) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_deeply_nested(self): """Deeply nested tuples can be viewed as immutable.""" obj = (1,) for _ in range(100): obj = (obj,) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_tuple_with_set(self): """A tuple containing a mutable set cannot be viewed as immutable.""" obj = (1, {2, 3}) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_c_shallow_immutable_type(self): """A C type registered as shallow immutable can be viewed as immutable.""" import _test_reachable freeze(_test_reachable.ShallowImmutable) obj = (_test_reachable.ShallowImmutable(1),) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_c_shallow_immutable_with_mutable_referent(self): """A C shallow immutable containing a mutable object cannot be viewed as immutable.""" import _test_reachable freeze(_test_reachable.ShallowImmutable) obj = (_test_reachable.ShallowImmutable([1, 2]),) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_deeply_nested_no_stack_overflow(self): """Very deep nesting should not cause a stack overflow.""" obj = (1,) for _ in range(10000): obj = (obj,) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) if __name__ == '__main__': diff --git a/Lib/test/test_freeze/test_module_proxy.py b/Lib/test/test_freeze/test_module_proxy.py index 5ea5cc4359ca37..d94ccd538e3c7a 100644 --- a/Lib/test/test_freeze/test_module_proxy.py +++ b/Lib/test/test_freeze/test_module_proxy.py @@ -1,7 +1,7 @@ import sys import unittest -from immutable import freeze, isfrozen, ImmutableModule +from immutable import freeze, is_frozen, ImmutableModule class TestModuleProxy(unittest.TestCase): def setUp(self): @@ -10,7 +10,7 @@ def setUp(self): def test_freeze_function_with_random_module_creates_proxy(self): import random - self.assertFalse(isfrozen(random)) + self.assertFalse(is_frozen(random)) def coin(): return random.random() @@ -18,20 +18,20 @@ def coin(): freeze(coin) captured_random = coin.__closure__[0].cell_contents - self.assertTrue(isfrozen(captured_random)) - self.assertTrue(isfrozen(random)) + self.assertTrue(is_frozen(captured_random)) + self.assertTrue(is_frozen(random)) self.assertIsInstance(captured_random, ImmutableModule) self.assertIsInstance(random, ImmutableModule) self.assertIn("random", sys.mut_modules) mut_random = sys.mut_modules["random"] self.assertIsNot(mut_random, captured_random) - self.assertFalse(isfrozen(mut_random)) + self.assertFalse(is_frozen(mut_random)) self.assertIsInstance(mut_random, sys.__class__) def test_random_state_remains_mutable_via_proxy(self): import random - self.assertFalse(isfrozen(random)) + self.assertFalse(is_frozen(random)) def coin(): return random.random() @@ -48,7 +48,7 @@ def coin(): def test_proxy_attribute_writes_delegate_to_mutable_module(self): import random - self.assertFalse(isfrozen(random)) + self.assertFalse(is_frozen(random)) def coin(): return random.random() diff --git a/Lib/test/test_freeze/test_multi_freeze.py b/Lib/test/test_freeze/test_multi_freeze.py index 86a4d8f5d2d9f3..f2f2416237a320 100644 --- a/Lib/test/test_freeze/test_multi_freeze.py +++ b/Lib/test/test_freeze/test_multi_freeze.py @@ -2,7 +2,7 @@ import unittest from immutable import ( - freeze, isfrozen, set_freezable, + freeze, is_frozen, set_freezable, FREEZABLE_EXPLICIT, FREEZABLE_NO, FREEZABLE_YES, ) @@ -23,21 +23,21 @@ def test_single_arg(self): C = make_freezable_class() obj = C() freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_two_args(self): C = make_freezable_class() a, b = C(), C() freeze(a, b) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) def test_many_args(self): C = make_freezable_class() objs = [C() for _ in range(10)] freeze(*objs) for obj in objs: - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_zero_args_raises(self): with self.assertRaises(TypeError): @@ -48,9 +48,9 @@ def test_already_frozen_skipped(self): C = make_freezable_class() a, b = C(), C() freeze(a) - self.assertTrue(isfrozen(a)) + self.assertTrue(is_frozen(a)) freeze(a, b) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(b)) def test_all_already_frozen(self): """Calling freeze on objects that are all already frozen is a no-op.""" @@ -59,8 +59,8 @@ def test_all_already_frozen(self): freeze(a) freeze(b) freeze(a, b) # should not raise - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) class TestMultiFreezeSharedGraph(unittest.TestCase): @@ -74,9 +74,9 @@ def test_shared_child(self): a.child = child b.child = child freeze(a, b) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) - self.assertTrue(isfrozen(child)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) + self.assertTrue(is_frozen(child)) def test_cross_references(self): """Roots that reference each other.""" @@ -85,8 +85,8 @@ def test_cross_references(self): a.other = b b.other = a freeze(a, b) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) class TestMultiFreezeExplicit(unittest.TestCase): @@ -98,7 +98,7 @@ def test_explicit_as_single_root(self): obj = C() set_freezable(obj, FREEZABLE_EXPLICIT) freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_explicit_as_child_fails(self): """EXPLICIT object reached as child (not a root) is rejected.""" @@ -108,7 +108,7 @@ def test_explicit_as_child_fails(self): set_freezable(child, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(child)) + self.assertFalse(is_frozen(child)) def test_explicit_as_one_of_multiple_roots(self): """EXPLICIT object listed as a root in multi-arg freeze succeeds.""" @@ -117,8 +117,8 @@ def test_explicit_as_one_of_multiple_roots(self): a.child = b set_freezable(b, FREEZABLE_EXPLICIT) freeze(a, b) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) def test_multiple_explicit_roots(self): """Multiple EXPLICIT objects all passed as roots.""" @@ -127,8 +127,8 @@ def test_multiple_explicit_roots(self): set_freezable(a, FREEZABLE_EXPLICIT) set_freezable(b, FREEZABLE_EXPLICIT) freeze(a, b) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) def test_explicit_child_not_in_roots_fails(self): """EXPLICIT child reachable from one root but not itself a root.""" @@ -139,7 +139,7 @@ def test_explicit_child_not_in_roots_fails(self): # c is not in the roots list, so it should fail with self.assertRaises(TypeError): freeze(a, b) - self.assertFalse(isfrozen(c)) + self.assertFalse(is_frozen(c)) class TestMultiFreezeNotFreezable(unittest.TestCase): @@ -171,8 +171,8 @@ def test_not_freezable_root_leaves_others_unfrozen(self): set_freezable(b, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(a, b) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) def test_not_freezable_child_leaves_parent_unfrozen(self): """When a child is FREEZABLE_NO, the parent root stays unfrozen.""" @@ -182,8 +182,8 @@ def test_not_freezable_child_leaves_parent_unfrozen(self): set_freezable(child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(parent)) - self.assertFalse(isfrozen(child)) + self.assertFalse(is_frozen(parent)) + self.assertFalse(is_frozen(child)) def test_not_freezable_child_leaves_all_roots_unfrozen(self): """Multi-root: one root's child is FREEZABLE_NO, all roots stay unfrozen.""" @@ -193,9 +193,9 @@ def test_not_freezable_child_leaves_all_roots_unfrozen(self): set_freezable(bad_child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(a, b) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(bad_child)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(bad_child)) def test_not_freezable_child_bad_root_last(self): """Bad root listed last — good root traversed first, still rolled back.""" @@ -205,9 +205,9 @@ def test_not_freezable_child_bad_root_last(self): set_freezable(bad_child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(good, bad_parent) - self.assertFalse(isfrozen(good)) - self.assertFalse(isfrozen(bad_parent)) - self.assertFalse(isfrozen(bad_child)) + self.assertFalse(is_frozen(good)) + self.assertFalse(is_frozen(bad_parent)) + self.assertFalse(is_frozen(bad_child)) def test_not_freezable_child_bad_root_first(self): """Bad root listed first — good root traversed after, still rolled back.""" @@ -217,9 +217,9 @@ def test_not_freezable_child_bad_root_first(self): set_freezable(bad_child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(bad_parent, good) - self.assertFalse(isfrozen(good)) - self.assertFalse(isfrozen(bad_parent)) - self.assertFalse(isfrozen(bad_child)) + self.assertFalse(is_frozen(good)) + self.assertFalse(is_frozen(bad_parent)) + self.assertFalse(is_frozen(bad_child)) def test_explicit_child_not_root_leaves_all_unfrozen(self): """EXPLICIT child not listed as root blocks freeze; nothing frozen.""" @@ -229,9 +229,9 @@ def test_explicit_child_not_root_leaves_all_unfrozen(self): set_freezable(explicit_child, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(a, b) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(explicit_child)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(explicit_child)) def test_explicit_child_bad_root_last(self): """EXPLICIT blocker's parent listed last — good root rolled back.""" @@ -241,9 +241,9 @@ def test_explicit_child_bad_root_last(self): set_freezable(explicit_child, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(good, parent) - self.assertFalse(isfrozen(good)) - self.assertFalse(isfrozen(parent)) - self.assertFalse(isfrozen(explicit_child)) + self.assertFalse(is_frozen(good)) + self.assertFalse(is_frozen(parent)) + self.assertFalse(is_frozen(explicit_child)) def test_explicit_child_bad_root_first(self): """EXPLICIT blocker's parent listed first — good root rolled back.""" @@ -253,9 +253,9 @@ def test_explicit_child_bad_root_first(self): set_freezable(explicit_child, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(parent, good) - self.assertFalse(isfrozen(good)) - self.assertFalse(isfrozen(parent)) - self.assertFalse(isfrozen(explicit_child)) + self.assertFalse(is_frozen(good)) + self.assertFalse(is_frozen(parent)) + self.assertFalse(is_frozen(explicit_child)) def test_many_roots_one_bad_none_frozen(self): """Many freezable roots plus one FREEZABLE_NO: none get frozen.""" @@ -266,8 +266,8 @@ def test_many_roots_one_bad_none_frozen(self): with self.assertRaises(TypeError): freeze(*good, bad) for obj in good: - self.assertFalse(isfrozen(obj)) - self.assertFalse(isfrozen(bad)) + self.assertFalse(is_frozen(obj)) + self.assertFalse(is_frozen(bad)) class TestMultiFreezeExplicitNested(unittest.TestCase): @@ -282,8 +282,8 @@ def test_explicit_nested_in_root_without_being_root_fails(self): set_freezable(inner, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(outer) - self.assertFalse(isfrozen(outer)) - self.assertFalse(isfrozen(inner)) + self.assertFalse(is_frozen(outer)) + self.assertFalse(is_frozen(inner)) def test_explicit_nested_in_root_and_also_root_succeeds(self): """An EXPLICIT child nested inside another root succeeds when also a root.""" @@ -293,8 +293,8 @@ def test_explicit_nested_in_root_and_also_root_succeeds(self): outer.inner = inner set_freezable(inner, FREEZABLE_EXPLICIT) freeze(outer, inner) - self.assertTrue(isfrozen(outer)) - self.assertTrue(isfrozen(inner)) + self.assertTrue(is_frozen(outer)) + self.assertTrue(is_frozen(inner)) def test_explicit_deeply_nested_as_root_succeeds(self): """Deeply nested EXPLICIT object succeeds when listed as root.""" @@ -304,9 +304,9 @@ def test_explicit_deeply_nested_as_root_succeeds(self): b.child = c set_freezable(c, FREEZABLE_EXPLICIT) freeze(a, c) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) - self.assertTrue(isfrozen(c)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) + self.assertTrue(is_frozen(c)) def test_explicit_deeply_nested_not_root_fails(self): """Deeply nested EXPLICIT object fails when not listed as root.""" @@ -317,9 +317,9 @@ def test_explicit_deeply_nested_not_root_fails(self): set_freezable(c, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(a) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(c)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(c)) def test_multiple_explicit_nested_all_roots(self): """Multiple EXPLICIT objects nested in a chain, all listed as roots.""" @@ -330,9 +330,9 @@ def test_multiple_explicit_nested_all_roots(self): set_freezable(b, FREEZABLE_EXPLICIT) set_freezable(c, FREEZABLE_EXPLICIT) freeze(a, b, c) - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) - self.assertTrue(isfrozen(c)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) + self.assertTrue(is_frozen(c)) def test_multiple_explicit_nested_one_missing_from_roots(self): """Two EXPLICIT in chain but only one is a root — freeze fails, nothing frozen.""" @@ -345,9 +345,9 @@ def test_multiple_explicit_nested_one_missing_from_roots(self): # b is a root but c is not with self.assertRaises(TypeError): freeze(a, b) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(c)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(c)) class TestFreezeReturnValue(unittest.TestCase): diff --git a/Lib/test/test_freeze/test_prefreeze.py b/Lib/test/test_freeze/test_prefreeze.py index a81973957f4fff..2bf5f492f27c8c 100644 --- a/Lib/test/test_freeze/test_prefreeze.py +++ b/Lib/test/test_freeze/test_prefreeze.py @@ -1,6 +1,6 @@ import unittest -from immutable import freeze, isfrozen, set_freezable, FREEZABLE_NO +from immutable import freeze, is_frozen, set_freezable, FREEZABLE_NO class TestPreFreezeHook(unittest.TestCase): @@ -16,7 +16,7 @@ def __pre_freeze__(self): freeze(obj) self.assertEqual(obj.hook_calls, 1) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_prefreeze_hook_runs_before_object_is_frozen(self): class C: @@ -24,13 +24,13 @@ def __init__(self): self.was_frozen_inside_hook = None def __pre_freeze__(self): - self.was_frozen_inside_hook = isfrozen(obj) + self.was_frozen_inside_hook = is_frozen(obj) obj = C() freeze(obj) self.assertIs(obj.was_frozen_inside_hook, False) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_prefreeze_hook_remains_called_after_failure(self): class C: @@ -50,7 +50,7 @@ def __pre_freeze__(self): freeze(obj) self.assertEqual(obj.hook_calls, 1) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_nested_freeze(self): class A: @@ -65,9 +65,9 @@ def __pre_freeze__(self): freeze(a) # Objects frozen by nested freeze calls should remain frozen - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(a.field)) - self.assertTrue(isfrozen(a.field.field)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(a.field)) + self.assertTrue(is_frozen(a.field.field)) def test_nested_cycle(self): class A: @@ -88,11 +88,11 @@ def __pre_freeze__(self): freeze(a) # Check the objects are frozen - self.assertTrue(isfrozen(a)) - self.assertTrue(isfrozen(b)) - self.assertTrue(isfrozen(c)) - self.assertTrue(isfrozen(d)) - self.assertTrue(isfrozen(e)) + self.assertTrue(is_frozen(a)) + self.assertTrue(is_frozen(b)) + self.assertTrue(is_frozen(c)) + self.assertTrue(is_frozen(d)) + self.assertTrue(is_frozen(e)) def test_nested_freeze_stays_frozen_on_fail(self): class A: @@ -111,8 +111,8 @@ def __pre_freeze__(self): freeze(a) # Objects frozen by nested freeze calls should remain frozen - self.assertFalse(isfrozen(a)) - self.assertTrue(isfrozen(a.freezable)) + self.assertFalse(is_frozen(a)) + self.assertTrue(is_frozen(a.freezable)) def test_pre_freeze_can_stop_freezing(self): class A: @@ -126,12 +126,12 @@ def __pre_freeze__(self): a = A(True) with self.assertRaises(ValueError): freeze(a) - self.assertFalse(isfrozen(a)) + self.assertFalse(is_frozen(a)) # This should succeed, since the pre-freeze succeeds a = A(False) freeze(a) - self.assertTrue(isfrozen(a)) + self.assertTrue(is_frozen(a)) def test_pre_freeze_self(self): class A: @@ -149,8 +149,8 @@ def __pre_freeze__(self): freeze(lst) # a should remain frozen due to its pre-freeze - self.assertTrue(isfrozen(a)) - self.assertFalse(isfrozen(b)) + self.assertTrue(is_frozen(a)) + self.assertFalse(is_frozen(b)) if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_freeze/test_reachable.py b/Lib/test/test_freeze/test_reachable.py index 7f5b55dcdfefb3..0f0ec11aac1179 100644 --- a/Lib/test/test_freeze/test_reachable.py +++ b/Lib/test/test_freeze/test_reachable.py @@ -1,13 +1,13 @@ import unittest import sys -from immutable import freeze, isfrozen +from immutable import freeze, is_frozen class BaseObjectTest(unittest.TestCase): def test_correct_traverse_visit_once(self): from _test_reachable import HasTraverseNoReachableHeap # Make sure this is a fresh import - self.assertFalse(isfrozen(HasTraverseNoReachableHeap)) + self.assertFalse(is_frozen(HasTraverseNoReachableHeap)) HasTraverseNoReachableHeap.a = HasTraverseNoReachableHeap() HasTraverseNoReachableHeap.b = HasTraverseNoReachableHeap() @@ -23,12 +23,12 @@ def test_traverse_misses_type(self): from _test_reachable import IncorrectTraverseNoReachableHeap # Make sure this is a fresh import - self.assertFalse(isfrozen(IncorrectTraverseNoReachableHeap)) + self.assertFalse(is_frozen(IncorrectTraverseNoReachableHeap)) # Freezing an instance should also freeze the type, even if # the traverse forgot to visit the type freeze(IncorrectTraverseNoReachableHeap()) - self.assertTrue(isfrozen(IncorrectTraverseNoReachableHeap)) + self.assertTrue(is_frozen(IncorrectTraverseNoReachableHeap)) # Unimport the module sys.modules.pop("_test_reachable", None) diff --git a/Lib/test/test_freeze/test_rollback.py b/Lib/test/test_freeze/test_rollback.py index 4c1ad376bd63b3..a8fb3830556e54 100644 --- a/Lib/test/test_freeze/test_rollback.py +++ b/Lib/test/test_freeze/test_rollback.py @@ -4,7 +4,7 @@ import unittest import weakref from immutable import ( - freeze, isfrozen, set_freezable, + freeze, is_frozen, set_freezable, FREEZABLE_NO, FREEZABLE_YES, ) @@ -28,8 +28,8 @@ def test_parent_with_not_freezable_child(self): set_freezable(child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(parent)) - self.assertFalse(isfrozen(child)) + self.assertFalse(is_frozen(parent)) + self.assertFalse(is_frozen(child)) def test_deep_chain_leaf_not_freezable(self): """a -> b -> c -> bad: all should be unfrozen.""" @@ -41,10 +41,10 @@ def test_deep_chain_leaf_not_freezable(self): set_freezable(bad, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(a) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(c)) - self.assertFalse(isfrozen(bad)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(c)) + self.assertFalse(is_frozen(bad)) def test_not_freezable_root(self): C = make_freezable_class() @@ -52,7 +52,7 @@ def test_not_freezable_root(self): set_freezable(obj, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(obj) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) class TestRollbackCycle(unittest.TestCase): @@ -68,9 +68,9 @@ def test_cycle_with_not_freezable_child(self): set_freezable(bad, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(a) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(bad)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(bad)) def test_three_cycle_with_not_freezable_child(self): """a -> b -> c -> a, c -> bad: all unfrozen.""" @@ -83,10 +83,10 @@ def test_three_cycle_with_not_freezable_child(self): set_freezable(bad, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(a) - self.assertFalse(isfrozen(a)) - self.assertFalse(isfrozen(b)) - self.assertFalse(isfrozen(c)) - self.assertFalse(isfrozen(bad)) + self.assertFalse(is_frozen(a)) + self.assertFalse(is_frozen(b)) + self.assertFalse(is_frozen(c)) + self.assertFalse(is_frozen(bad)) class TestRollbackPreservesExisting(unittest.TestCase): @@ -96,7 +96,7 @@ def test_previously_frozen_unaffected(self): C = make_freezable_class() already = C() freeze(already) - self.assertTrue(isfrozen(already)) + self.assertTrue(is_frozen(already)) parent = C() child = C() @@ -104,9 +104,9 @@ def test_previously_frozen_unaffected(self): set_freezable(child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(parent)) + self.assertFalse(is_frozen(parent)) # The previously-frozen object should still be frozen. - self.assertTrue(isfrozen(already)) + self.assertTrue(is_frozen(already)) class TestRollbackNormalFreezeStillWorks(unittest.TestCase): @@ -120,12 +120,12 @@ def test_freeze_after_rollback(self): parent.child = bad with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(parent)) + self.assertFalse(is_frozen(parent)) # Now freeze something else successfully good = C() freeze(good) - self.assertTrue(isfrozen(good)) + self.assertTrue(is_frozen(good)) def test_refreeze_after_rollback(self): """Object that was rolled back can be frozen after removing the blocker.""" @@ -136,12 +136,12 @@ def test_refreeze_after_rollback(self): set_freezable(child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(parent)) + self.assertFalse(is_frozen(parent)) # Remove the blocker and try again del parent.child freeze(parent) - self.assertTrue(isfrozen(parent)) + self.assertTrue(is_frozen(parent)) class TestRollbackRefcounts(unittest.TestCase): @@ -230,8 +230,8 @@ def test_weakref_in_graph_collected_after_rollback(self): wr_target = weakref.ref(target) with self.assertRaises(TypeError): freeze(holder) - self.assertFalse(isfrozen(holder)) - self.assertFalse(isfrozen(target)) + self.assertFalse(is_frozen(holder)) + self.assertFalse(is_frozen(target)) del holder, target, bad gc.collect() self.assertIsNone(wr_holder()) diff --git a/Lib/test/test_freeze/test_set_freezable.py b/Lib/test/test_freeze/test_set_freezable.py index 6c1a6cafee6f7e..7a4870fdc19cdc 100644 --- a/Lib/test/test_freeze/test_set_freezable.py +++ b/Lib/test/test_freeze/test_set_freezable.py @@ -2,7 +2,7 @@ import unittest import weakref from immutable import ( - freeze, isfrozen, set_freezable, + freeze, is_frozen, set_freezable, FREEZABLE_YES, FREEZABLE_NO, FREEZABLE_EXPLICIT, FREEZABLE_PROXY, ) @@ -23,7 +23,7 @@ def test_freeze_succeeds(self): obj = C() set_freezable(obj, FREEZABLE_YES) freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_freeze_as_child_succeeds(self): C = make_freezable_class() @@ -32,7 +32,7 @@ def test_freeze_as_child_succeeds(self): parent.child = child set_freezable(child, FREEZABLE_YES) freeze(parent) - self.assertTrue(isfrozen(child)) + self.assertTrue(is_frozen(child)) class TestSetFreezableNo(unittest.TestCase): @@ -44,7 +44,7 @@ def test_freeze_raises(self): set_freezable(obj, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(obj) - self.assertFalse(isfrozen(obj)) + self.assertFalse(is_frozen(obj)) def test_freeze_as_child_raises(self): C = make_freezable_class() @@ -54,8 +54,8 @@ def test_freeze_as_child_raises(self): set_freezable(child, FREEZABLE_NO) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(child)) - self.assertFalse(isfrozen(parent)) + self.assertFalse(is_frozen(child)) + self.assertFalse(is_frozen(parent)) class TestSetFreezableExplicit(unittest.TestCase): @@ -66,7 +66,7 @@ def test_direct_freeze_succeeds(self): obj = C() set_freezable(obj, FREEZABLE_EXPLICIT) freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_child_freeze_raises(self): C = make_freezable_class() @@ -76,7 +76,7 @@ def test_child_freeze_raises(self): set_freezable(child, FREEZABLE_EXPLICIT) with self.assertRaises(TypeError): freeze(parent) - self.assertFalse(isfrozen(child)) + self.assertFalse(is_frozen(child)) class TestSetFreezableProxy(unittest.TestCase): @@ -132,14 +132,14 @@ def test_override_status(self): # Override to YES set_freezable(obj, FREEZABLE_YES) freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) def test_unset_object_uses_default(self): # An object with no set_freezable should use existing freeze logic. C = make_freezable_class() obj = C() freeze(obj) - self.assertTrue(isfrozen(obj)) + self.assertTrue(is_frozen(obj)) class TestSetFreezableStorage(unittest.TestCase): diff --git a/Lib/test/test_freeze/test_weakref.py b/Lib/test/test_freeze/test_weakref.py index a31cd0ed2f2bd7..653ade08a3cc17 100644 --- a/Lib/test/test_freeze/test_weakref.py +++ b/Lib/test/test_freeze/test_weakref.py @@ -2,7 +2,7 @@ import unittest import weakref -from immutable import freeze, isfrozen +from immutable import freeze, is_frozen class A: @@ -40,7 +40,7 @@ def test_weakref_to_frozen_object(self): wr = weakref.ref(a) # The weakref should be frozen to ensure atomic refcounting. # FIXME(Immutable): Freezing a weakref currently makes it strong. - # self.assertTrue(isfrozen(wr)) + # self.assertTrue(is_frozen(wr)) self.assertEqual(sys.getrefcount(wr), sys.getrefcount(baseline)) def test_weakref_to_frozen_object_callback(self): @@ -49,7 +49,7 @@ def test_weakref_to_frozen_object_callback(self): freeze(a) wr = weakref.ref(a, dummy_callback) # The weakref should have had its refcount pre-emptively incremented. - self.assertFalse(isfrozen(wr)) + self.assertFalse(is_frozen(wr)) self.assertEqual(sys.getrefcount(wr), sys.getrefcount(baseline) + 1) def test_freeze_object_with_weakref(self): @@ -59,7 +59,7 @@ def test_freeze_object_with_weakref(self): freeze(a) # The weakref should be frozen to ensure atomic refcounting. # FIXME(Immutable): Freezing a weakref currently makes it strong. - # self.assertTrue(isfrozen(wr)) + # self.assertTrue(is_frozen(wr)) self.assertEqual(sys.getrefcount(wr), sys.getrefcount(baseline)) def test_freeze_object_with_weakref_callback(self): @@ -68,7 +68,7 @@ def test_freeze_object_with_weakref_callback(self): wr = weakref.ref(a, dummy_callback) freeze(a) # The weakref should have had its refcount pre-emptively incremented. - self.assertFalse(isfrozen(wr)) + self.assertFalse(is_frozen(wr)) self.assertEqual(sys.getrefcount(wr), sys.getrefcount(baseline) + 1) diff --git a/Modules/_immutablemodule.c b/Modules/_immutablemodule.c index c027fa66300fc9..e1495ff40728cf 100644 --- a/Modules/_immutablemodule.c +++ b/Modules/_immutablemodule.c @@ -104,7 +104,7 @@ _immutable_freeze_impl(PyObject *module, PyObject * const *args, } /*[clinic input] -_immutable.isfrozen +_immutable.is_frozen obj: object / @@ -115,8 +115,8 @@ side effect and True is returned. [clinic start generated code]*/ static PyObject * -_immutable_isfrozen(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=5857a038e2a68ed7 input=f60302e01ab45c4d]*/ +_immutable_is_frozen(PyObject *module, PyObject *obj) +/*[clinic end generated code: output=880efe7d38b137b5 input=97c61fe65ccb1574]*/ { int result = _PyImmutability_CanViewAsImmutable(obj); if (result < 0) { @@ -139,13 +139,14 @@ Set the freezable status of an object. Status values: FREEZABLE_YES (0): always freezable FREEZABLE_NO (1): never freezable - FREEZABLE_EXPLICIT (2): freezable only when freeze() is called directly on it + FREEZABLE_EXPLICIT (2): freezable only when freeze() is + called directly on it FREEZABLE_PROXY (3): reserved for future use [clinic start generated code]*/ static PyObject * _immutable_set_freezable_impl(PyObject *module, PyObject *obj, int status) -/*[clinic end generated code: output=73cad0b4df9a46f9 input=63df024c940ba301]*/ +/*[clinic end generated code: output=73cad0b4df9a46f9 input=6528458c547e93a8]*/ { if (_PyImmutability_SetFreezable(obj, status) < 0) { return NULL; diff --git a/Modules/clinic/_immutablemodule.c.h b/Modules/clinic/_immutablemodule.c.h index 2b9998ef2ffa71..822c10646719f5 100644 --- a/Modules/clinic/_immutablemodule.c.h +++ b/Modules/clinic/_immutablemodule.c.h @@ -40,8 +40,8 @@ _immutable_freeze(PyObject *module, PyObject *const *args, Py_ssize_t nargs) return return_value; } -PyDoc_STRVAR(_immutable_isfrozen__doc__, -"isfrozen($module, obj, /)\n" +PyDoc_STRVAR(_immutable_is_frozen__doc__, +"is_frozen($module, obj, /)\n" "--\n" "\n" "Check if an object is frozen (or can be viewed as immutable).\n" @@ -49,8 +49,8 @@ PyDoc_STRVAR(_immutable_isfrozen__doc__, "If the object graph can be viewed as immutable, it will be frozen as a\n" "side effect and True is returned."); -#define _IMMUTABLE_ISFROZEN_METHODDEF \ - {"isfrozen", (PyCFunction)_immutable_isfrozen, METH_O, _immutable_isfrozen__doc__}, +#define _IMMUTABLE_IS_FROZEN_METHODDEF \ + {"is_frozen", (PyCFunction)_immutable_is_frozen, METH_O, _immutable_is_frozen__doc__}, PyDoc_STRVAR(_immutable_set_freezable__doc__, "set_freezable($module, obj, status, /)\n" @@ -61,7 +61,8 @@ PyDoc_STRVAR(_immutable_set_freezable__doc__, "Status values:\n" " FREEZABLE_YES (0): always freezable\n" " FREEZABLE_NO (1): never freezable\n" -" FREEZABLE_EXPLICIT (2): freezable only when freeze() is called directly on it\n" +" FREEZABLE_EXPLICIT (2): freezable only when freeze() is\n" +" called directly on it\n" " FREEZABLE_PROXY (3): reserved for future use"); #define _IMMUTABLE_SET_FREEZABLE_METHODDEF \ @@ -90,4 +91,4 @@ _immutable_set_freezable(PyObject *module, PyObject *const *args, Py_ssize_t nar exit: return return_value; } -/*[clinic end generated code: output=c6e7830815e38208 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=90e8e33edcb8a911 input=a9049054013a1b77]*/ From 6f541faada34407b4798d357a6dd8e99fbdd5e4f Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 15:14:02 +0100 Subject: [PATCH 2/6] Remove `_PyImmutability_RegisterFreezable` in favour of `set_freezable` --- Include/cpython/immutability.h | 10 ++- Include/internal/pycore_immutability.h | 8 --- Lib/test/test_freeze/test_set_freezable.py | 5 -- Modules/_collectionsmodule.c | 4 +- Modules/_ctypes/_ctypes.c | 2 +- Modules/_datetimemodule.c | 2 +- Modules/_decimal/_decimal.c | 7 +- Modules/_elementtree.c | 2 +- Modules/_immutablemodule.c | 27 +------ Modules/_struct.c | 2 +- Modules/_test_reachable.c | 12 ++-- Modules/arraymodule.c | 2 +- Modules/clinic/_immutablemodule.c.h | 11 +-- Python/immutability.c | 82 +++------------------- Python/pystate.c | 1 - 15 files changed, 37 insertions(+), 140 deletions(-) diff --git a/Include/cpython/immutability.h b/Include/cpython/immutability.h index abd5b6a1aa6d0d..91815ac757fc01 100644 --- a/Include/cpython/immutability.h +++ b/Include/cpython/immutability.h @@ -2,12 +2,18 @@ # error "this header file must not be included directly" #endif +typedef enum { + _Py_FREEZABLE_YES = 0, + _Py_FREEZABLE_NO = 1, + _Py_FREEZABLE_EXPLICIT = 2, + _Py_FREEZABLE_PROXY = 3, +} _Py_freezable_status; + PyAPI_DATA(PyTypeObject) _PyNotFreezable_Type; PyAPI_FUNC(int) _PyImmutability_Freeze(PyObject*); PyAPI_FUNC(int) _PyImmutability_FreezeMany(PyObject *const *, Py_ssize_t); -PyAPI_FUNC(int) _PyImmutability_RegisterFreezable(PyTypeObject*); PyAPI_FUNC(int) _PyImmutability_RegisterShallowImmutable(PyTypeObject*); PyAPI_FUNC(int) _PyImmutability_CanViewAsImmutable(PyObject*); -PyAPI_FUNC(int) _PyImmutability_SetFreezable(PyObject *, int); +PyAPI_FUNC(int) _PyImmutability_SetFreezable(PyObject *, _Py_freezable_status); PyAPI_FUNC(int) _PyImmutability_GetFreezable(PyObject *); diff --git a/Include/internal/pycore_immutability.h b/Include/internal/pycore_immutability.h index cc8656533ac91b..be8c1d51592b64 100644 --- a/Include/internal/pycore_immutability.h +++ b/Include/internal/pycore_immutability.h @@ -10,16 +10,8 @@ extern "C" { typedef struct _Py_hashtable_t _Py_hashtable_t; -typedef enum { - _Py_FREEZABLE_YES = 0, - _Py_FREEZABLE_NO = 1, - _Py_FREEZABLE_EXPLICIT = 2, - _Py_FREEZABLE_PROXY = 3, -} _Py_freezable_status; - struct _Py_immutability_state { int late_init_done; - PyObject *freezable_types; _Py_hashtable_t *shallow_immutable_types; PyObject *destroy_cb; _Py_hashtable_t *warned_types; diff --git a/Lib/test/test_freeze/test_set_freezable.py b/Lib/test/test_freeze/test_set_freezable.py index 7a4870fdc19cdc..0ad081321af5c0 100644 --- a/Lib/test/test_freeze/test_set_freezable.py +++ b/Lib/test/test_freeze/test_set_freezable.py @@ -172,16 +172,11 @@ def test_attr_storage_updates_on_override(self): def test_ob_flags_fallback_for_slots_only(self): # Objects with __slots__ but no __dict__ use ob_flags for # instance-level set_freezable on 64-bit. - # Use _immutable.register_freezable (the low-level C API) to - # register the type without setting __freezable__, so the - # per-instance ob_flags path is tested in isolation. import sys - import _immutable if sys.maxsize <= 2**31: self.skipTest("ob_flags fallback not available on 32-bit") class S: __slots__ = ('__weakref__', 'x') - _immutable.register_freezable(S) obj = S() set_freezable(obj, FREEZABLE_NO) # No __freezable__ attribute should be set on the instance diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index dad177ccc13d40..136adebd8f5168 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2904,11 +2904,11 @@ collections_exec(PyObject *module) { ADD_TYPE(module, &dequereviter_spec, state->dequereviter_type, NULL); ADD_TYPE(module, &tuplegetter_spec, state->tuplegetter_type, NULL); - if(_PyImmutability_RegisterFreezable(state->deque_type) < 0){ + if(_PyImmutability_SetFreezable(state->deque_type, _Py_FREEZABLE_YES) < 0){ return -1; } - if(_PyImmutability_RegisterFreezable(state->defdict_type) < 0){ + if(_PyImmutability_SetFreezable(state->defdict_type, _Py_FREEZABLE_YES) < 0){ return -1; } diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index e2dc90912ca072..53ee8b66167259 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -6315,7 +6315,7 @@ _ctypes_add_types(PyObject *mod) #define REGISTER_FREEZEABLE(TYPE_EXPR) \ do { \ PyTypeObject *type = (TYPE_EXPR); \ - if(_PyImmutability_RegisterFreezable(type) < 0){ \ + if(_PyImmutability_SetFreezable(_PyObject_CAST(type), _Py_FREEZABLE_YES) < 0){ \ return -1; \ } \ } while (0) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index db67e335c2309f..eb4958f0c7ce1f 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7498,7 +7498,7 @@ _PyDateTime_InitTypes(PyInterpreterState *interp) } // TODO(Immutable): Revisit after PLDI deadline. - if(_PyImmutability_RegisterFreezable(capi_types[i]) < 0) { + if(_PyImmutability_SetFreezable(capi_types[i], _Py_FREEZABLE_YES) < 0) { return _PyStatus_ERR("could not freeze static types"); } } diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index d2582ba6271f04..df0efc21c3567e 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7772,10 +7772,9 @@ _decimal_exec(PyObject *m) CHECK_INT(PyModule_AddType(m, state->PyDecContext_Type)); CHECK_INT(PyModule_AddType(m, state->DecimalTuple)); - CHECK_INT(_PyImmutability_RegisterFreezable(state->PyDec_Type)); - CHECK_INT(_PyImmutability_RegisterFreezable(state->PyDecContext_Type)); - // TODO(Immutable): This was not needed in 3.12. Review! - CHECK_INT(_PyImmutability_RegisterFreezable(state->PyDecSignalDict_Type)); + CHECK_INT(_PyImmutability_SetFreezable(state->PyDec_Type, _Py_FREEZABLE_YES)); + CHECK_INT(_PyImmutability_SetFreezable(state->PyDecContext_Type, _Py_FREEZABLE_YES)); + CHECK_INT(_PyImmutability_SetFreezable(state->PyDecSignalDict_Type, _Py_FREEZABLE_YES)); /* Create top level exception */ ASSIGN_PTR(state->DecimalException, PyErr_NewException( diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index ad2cd7dc55fc6c..aff9b45b8c2aa3 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -4471,7 +4471,7 @@ module_exec(PyObject *m) CREATE_TYPE(m, st->Element_Type, &element_spec); CREATE_TYPE(m, st->XMLParser_Type, &xmlparser_spec); - if (_PyImmutability_RegisterFreezable(st->Element_Type) != 0) { + if (_PyImmutability_SetFreezable((PyObject*)st->Element_Type, _Py_FREEZABLE_YES) != 0) { goto error; } diff --git a/Modules/_immutablemodule.c b/Modules/_immutablemodule.c index e1495ff40728cf..0a1a8f9fec2406 100644 --- a/Modules/_immutablemodule.c +++ b/Modules/_immutablemodule.c @@ -54,30 +54,6 @@ immutable_free(void *module) immutable_clear((PyObject *)module); } -/*[clinic input] -_immutable.register_freezable - obj: object - / - -Register a type as freezable. -[clinic start generated code]*/ - -static PyObject * -_immutable_register_freezable(PyObject *module, PyObject *obj) -/*[clinic end generated code: output=7a68ab35ee36a572 input=48ad5294977fe780]*/ -{ - if(!PyType_Check(obj)){ - PyErr_SetString(PyExc_TypeError, "Expected a type"); - return NULL; - } - - if(_PyImmutability_RegisterFreezable((PyTypeObject *)obj) < 0){ - return NULL; - } - - Py_RETURN_NONE; -} - /*[clinic input] _immutable.freeze *args: array @@ -179,9 +155,8 @@ PyDoc_STRVAR(immutable_module_doc, "making them immutable at runtime."); static struct PyMethodDef immutable_methods[] = { - _IMMUTABLE_REGISTER_FREEZABLE_METHODDEF _IMMUTABLE_FREEZE_METHODDEF - _IMMUTABLE_ISFROZEN_METHODDEF + _IMMUTABLE_IS_FROZEN_METHODDEF _IMMUTABLE_SET_FREEZABLE_METHODDEF { NULL, NULL } }; diff --git a/Modules/_struct.c b/Modules/_struct.c index 0a3b632b2afd54..91961ada84e7a6 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2705,7 +2705,7 @@ _structmodule_exec(PyObject *m) if (PyModule_AddType(m, (PyTypeObject *)state->PyStructType) < 0) { return -1; } - if (_PyImmutability_RegisterFreezable((PyTypeObject *)state->PyStructType) < 0){ + if (_PyImmutability_SetFreezable(state->PyStructType, _Py_FREEZABLE_YES) < 0){ return -1; } diff --git a/Modules/_test_reachable.c b/Modules/_test_reachable.c index ef66d5a88e7f4d..cace3037f888f2 100644 --- a/Modules/_test_reachable.c +++ b/Modules/_test_reachable.c @@ -366,7 +366,7 @@ _test_reachable_exec(PyObject *module) HasTraverseNoReachable_Type.tp_reachable = NULL; if (PyModule_AddType(module, &HasTraverseNoReachable_Type) != 0) return -1; - if (_PyImmutability_RegisterFreezable(&HasTraverseNoReachable_Type) < 0) + if (_PyImmutability_SetFreezable((PyObject*)&HasTraverseNoReachable_Type, _Py_FREEZABLE_YES) < 0) return -1; htnrh_type = PyType_FromModuleAndSpec(module, &HasTraverseNoReachableHeap_spec, NULL); @@ -378,7 +378,7 @@ _test_reachable_exec(PyObject *module) Py_DECREF(htnrh_type); return -1; } - if (_PyImmutability_RegisterFreezable((PyTypeObject *)htnrh_type) < 0) { + if (_PyImmutability_SetFreezable((PyObject*)htnrh_type, _Py_FREEZABLE_YES) < 0) { Py_DECREF(htnrh_type); return -1; } @@ -393,7 +393,7 @@ _test_reachable_exec(PyObject *module) Py_DECREF(itnrh_type); return -1; } - if (_PyImmutability_RegisterFreezable((PyTypeObject *)itnrh_type) < 0) { + if (_PyImmutability_SetFreezable((PyObject*)itnrh_type, _Py_FREEZABLE_YES) < 0) { Py_DECREF(itnrh_type); return -1; } @@ -405,21 +405,21 @@ _test_reachable_exec(PyObject *module) NoTraverseNoReachable_Type.tp_reachable = NULL; if (PyModule_AddType(module, &NoTraverseNoReachable_Type) != 0) return -1; - if (_PyImmutability_RegisterFreezable(&NoTraverseNoReachable_Type) < 0) + if (_PyImmutability_SetFreezable((PyObject*)&NoTraverseNoReachable_Type, _Py_FREEZABLE_YES) < 0) return -1; if (PyType_Ready(&HasReachable_Type) < 0) return -1; if (PyModule_AddType(module, &HasReachable_Type) != 0) return -1; - if (_PyImmutability_RegisterFreezable(&HasReachable_Type) < 0) + if (_PyImmutability_SetFreezable((PyObject*)&HasReachable_Type, _Py_FREEZABLE_YES) < 0) return -1; if (PyType_Ready(&ShallowImmutable_Type) < 0) return -1; if (PyModule_AddType(module, &ShallowImmutable_Type) != 0) return -1; - if (_PyImmutability_RegisterFreezable(&ShallowImmutable_Type) < 0) + if (_PyImmutability_SetFreezable((PyObject*)&ShallowImmutable_Type, _Py_FREEZABLE_YES) < 0) return -1; if (_PyImmutability_RegisterShallowImmutable(&ShallowImmutable_Type) < 0) return -1; diff --git a/Modules/arraymodule.c b/Modules/arraymodule.c index 7bf15b700466da..835f07c332bb3f 100644 --- a/Modules/arraymodule.c +++ b/Modules/arraymodule.c @@ -3278,7 +3278,7 @@ array_modexec(PyObject *m) return -1; } - if(_PyImmutability_RegisterFreezable(state->ArrayType) < 0){ + if(_PyImmutability_SetFreezable((PyObject*)state->ArrayType, _Py_FREEZABLE_YES) < 0){ Py_DECREF((PyObject *)state->ArrayType); return -1; } diff --git a/Modules/clinic/_immutablemodule.c.h b/Modules/clinic/_immutablemodule.c.h index 822c10646719f5..762cf94d344181 100644 --- a/Modules/clinic/_immutablemodule.c.h +++ b/Modules/clinic/_immutablemodule.c.h @@ -4,15 +4,6 @@ preserve #include "pycore_modsupport.h" // _PyArg_CheckPositional() -PyDoc_STRVAR(_immutable_register_freezable__doc__, -"register_freezable($module, obj, /)\n" -"--\n" -"\n" -"Register a type as freezable."); - -#define _IMMUTABLE_REGISTER_FREEZABLE_METHODDEF \ - {"register_freezable", (PyCFunction)_immutable_register_freezable, METH_O, _immutable_register_freezable__doc__}, - PyDoc_STRVAR(_immutable_freeze__doc__, "freeze($module, /, *args)\n" "--\n" @@ -91,4 +82,4 @@ _immutable_set_freezable(PyObject *module, PyObject *const *args, Py_ssize_t nar exit: return return_value; } -/*[clinic end generated code: output=90e8e33edcb8a911 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=39afc4be55c6fa33 input=a9049054013a1b77]*/ diff --git a/Python/immutability.c b/Python/immutability.c index 9244d72a642064..552de53f8e999f 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -112,27 +112,9 @@ static PyMethodDef _destroy_def = { "_destroy", (PyCFunction) _destroy, METH_O }; -static PyObject * -type_weakref(struct _Py_immutability_state *state, PyObject *obj) -{ - if(state->destroy_cb == NULL){ - state->destroy_cb = PyCFunction_NewEx(&_destroy_def, state->freezable_types, NULL); - if (state->destroy_cb == NULL) { - return NULL; - } - } - - return PyWeakref_NewRef(obj, state->destroy_cb); -} - static int init_state(struct _Py_immutability_state *state) { - state->freezable_types = PySet_New(NULL); - if(state->freezable_types == NULL){ - return -1; - } - state->warned_types = _Py_hashtable_new( _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct); @@ -181,7 +163,7 @@ static struct _Py_immutability_state* get_immutable_state(void) { PyInterpreterState* interp = PyInterpreterState_Get(); struct _Py_immutability_state *state = &interp->immutability; - if(state->freezable_types == NULL){ + if(state->shallow_immutable_types == NULL){ if(init_state(state) == -1){ PyErr_SetString(PyExc_RuntimeError, "Failed to initialize immutability state"); return NULL; @@ -1517,20 +1499,6 @@ is_freezable_builtin(PyTypeObject *type) return false; } -static int -is_explicitly_freezable(struct _Py_immutability_state *state, PyObject *obj) -{ - int result = 0; - PyObject *ref = type_weakref(state, (PyObject *)obj->ob_type); - if(ref == NULL){ - return -1; - } - - result = PySet_Contains(state->freezable_types, ref); - Py_DECREF(ref); - return result; -} - static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, struct FreezeState *freeze_state) @@ -1566,17 +1534,6 @@ static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, return 0; } - // TODO(Immutable): Fail is type is not already frozen. - // This will require the test suite to be updated. - - int result = is_explicitly_freezable(state, obj); - if(result == -1){ - return -1; - } - else if(result == 1){ - return 0; - } - // TODO(Immutable): Visit what the right balance of making Python types immutable is. if(!_PyType_HasExtensionSlots(obj->ob_type)){ return 0; @@ -1592,28 +1549,7 @@ static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, } -int _PyImmutability_RegisterFreezable(PyTypeObject* tp) -{ - PyObject *ref; - int result; - struct _Py_immutability_state *state = get_immutable_state(); - if(state == NULL){ - PyErr_SetString(PyExc_RuntimeError, "Failed to initialize immutability state"); - return -1; - } - - ref = type_weakref(state, (PyObject*)tp); - if(ref == NULL){ - return -1; - } - - result = PySet_Add(state->freezable_types, ref); - Py_DECREF(ref); - return result; -} - - -int _PyImmutability_SetFreezable(PyObject *obj, int status) +int _PyImmutability_SetFreezable(PyObject *obj, _Py_freezable_status status) { if (status < _Py_FREEZABLE_YES || status > _Py_FREEZABLE_PROXY) { PyErr_Format(PyExc_ValueError, @@ -1633,18 +1569,22 @@ int _PyImmutability_SetFreezable(PyObject *obj, int status) return -1; } + assert(PyErr_Occurred() == NULL); int rc = PyObject_SetAttr(obj, &_Py_ID(__freezable__), value); Py_DECREF(value); if (rc == 0) { return 0; + } else { + // Attempting to set the attribute can cause different errors + // depending on the type implementation. We just clear it here + // and store the freezability in the object fields instead. + // This should be safe, since `PyErr` should never be set when + // this function is called. + PyErr_Clear(); } + // If the object doesn't support attribute setting, fall back // to ob_flags (64-bit only). - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { - return -1; - } - PyErr_Clear(); - #if SIZEOF_VOID_P > 4 // Store the freezable status in ob_flags bits 5-7. uint16_t flags = obj->ob_flags; diff --git a/Python/pystate.c b/Python/pystate.c index d13cf4ba96dee6..0fef8e4b138008 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -792,7 +792,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) assert(interp->imports.importlib == NULL); assert(interp->imports.import_func == NULL); - Py_CLEAR(interp->immutability.freezable_types); Py_CLEAR(interp->immutability.destroy_cb); if (interp->immutability.warned_types != NULL) { _Py_hashtable_destroy(interp->immutability.warned_types); From 6bcd48f04f28227eeee34750ad0cc8bdffa81ce6 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 15:24:27 +0100 Subject: [PATCH 3/6] Remove `NotFreezable` type --- Include/cpython/immutability.h | 2 -- Lib/immutable.py | 2 -- Lib/test/test_freeze/test_common.py | 7 +++++-- Lib/test/test_freeze/test_core.py | 11 +---------- Modules/_immutablemodule.c | 4 ---- Python/immutability.c | 23 ----------------------- 6 files changed, 6 insertions(+), 43 deletions(-) diff --git a/Include/cpython/immutability.h b/Include/cpython/immutability.h index 91815ac757fc01..637362662efb90 100644 --- a/Include/cpython/immutability.h +++ b/Include/cpython/immutability.h @@ -9,8 +9,6 @@ typedef enum { _Py_FREEZABLE_PROXY = 3, } _Py_freezable_status; -PyAPI_DATA(PyTypeObject) _PyNotFreezable_Type; - PyAPI_FUNC(int) _PyImmutability_Freeze(PyObject*); PyAPI_FUNC(int) _PyImmutability_FreezeMany(PyObject *const *, Py_ssize_t); PyAPI_FUNC(int) _PyImmutability_RegisterShallowImmutable(PyTypeObject*); diff --git a/Lib/immutable.py b/Lib/immutable.py index a778c5acd38272..9f35a5b7949e0a 100644 --- a/Lib/immutable.py +++ b/Lib/immutable.py @@ -11,7 +11,6 @@ freeze = _c.freeze is_frozen = _c.is_frozen set_freezable = _c.set_freezable -NotFreezable = getattr(_c, "NotFreezable", None) NotFreezableError = _c.NotFreezableError ImmutableModule = _c.ImmutableModule FREEZABLE_YES = _c.FREEZABLE_YES @@ -53,7 +52,6 @@ def frozen(cls): "freeze", "is_frozen", "set_freezable", - "NotFreezable", "NotFreezableError", "ImmutableModule", "FREEZABLE_YES", diff --git a/Lib/test/test_freeze/test_common.py b/Lib/test/test_freeze/test_common.py index 3854f15b329cee..e10161ea4fe9dd 100644 --- a/Lib/test/test_freeze/test_common.py +++ b/Lib/test/test_freeze/test_common.py @@ -1,5 +1,5 @@ import unittest -from immutable import freeze, is_frozen, NotFreezable +from immutable import freeze, is_frozen class BaseObjectTest(unittest.TestCase): @@ -26,11 +26,14 @@ def test_type_immutable(self): class BaseNotFreezableTest(unittest.TestCase): - def __init__(self, *args, obj=NotFreezable(), **kwargs): + def __init__(self, *args, obj=None, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self.obj = obj def test_not_freezable(self): + if self.obj == None: + return + with self.assertRaises(TypeError): freeze(self.obj) diff --git a/Lib/test/test_freeze/test_core.py b/Lib/test/test_freeze/test_core.py index f9091e1c85c04b..402bd442804258 100644 --- a/Lib/test/test_freeze/test_core.py +++ b/Lib/test/test_freeze/test_core.py @@ -1,5 +1,5 @@ import unittest -from immutable import freeze, NotFreezable, is_frozen +from immutable import freeze, is_frozen from .test_common import BaseNotFreezableTest, BaseObjectTest @@ -598,15 +598,6 @@ def f(a, **b): self.assertTrue(is_frozen(bdef)) -class TestNotFreezable(BaseNotFreezableTest): - class C(NotFreezable): - pass - - def __init__(self, *args, **kwargs): - obj = self.C() - super().__init__(*args, obj=obj, **kwargs) - - class TestInheritFromCType(unittest.TestCase): class C(list): pass diff --git a/Modules/_immutablemodule.c b/Modules/_immutablemodule.c index 0a1a8f9fec2406..dc20ec15f8ba93 100644 --- a/Modules/_immutablemodule.c +++ b/Modules/_immutablemodule.c @@ -187,10 +187,6 @@ immutable_exec(PyObject *module) { return -1; } - if (PyModule_AddType(module, &_PyNotFreezable_Type) != 0) { - return -1; - } - if (PyModule_AddType(module, &_PyImmModule_Type) != 0) { return -1; } diff --git a/Python/immutability.c b/Python/immutability.c index 552de53f8e999f..be5bfc3686b245 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -174,23 +174,6 @@ static struct _Py_immutability_state* get_immutable_state(void) } -PyDoc_STRVAR(notfreezable_doc, - "NotFreezable()\n\ - \n\ - Indicate that a type cannot be frozen."); - - -PyTypeObject _PyNotFreezable_Type = { - PyVarObject_HEAD_INIT(&PyType_Type, 0) - .tp_name = "NotFreezable", - .tp_doc = notfreezable_doc, - .tp_basicsize = sizeof(PyObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_BASETYPE, - .tp_new = PyType_GenericNew -}; - - static int push(PyObject* s, PyObject* item){ if(item == NULL){ return 0; @@ -1524,12 +1507,6 @@ static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, } } - // Check is object is subclass of NotFreezable - // TODO: Would be nice for this to be faster. - if (PyObject_IsInstance(obj, (PyObject *)&_PyNotFreezable_Type) == 1){ - goto error; - } - if(is_freezable_builtin(obj->ob_type)){ return 0; } From 9052028fc8b479b60f2e4fb6c618a3a81dd9977c Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 15:39:01 +0100 Subject: [PATCH 4/6] Remove `is_freezable_builtin` in favour of `set_freezable` --- Python/immutability.c | 91 +++++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 56 deletions(-) diff --git a/Python/immutability.c b/Python/immutability.c index be5bfc3686b245..227a455d06b153 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -154,8 +154,43 @@ int init_state(struct _Py_immutability_state *state) shallow_types[i], (void *)1) < 0) { return -1; } + if (_PyImmutability_SetFreezable(shallow_types[i], _Py_FREEZABLE_YES)) { + return -1; + } } + PyTypeObject *builtin_freezable_types[] = { + &PyType_Type, + &PyBaseObject_Type, + &PyFunction_Type, + &PyList_Type, + &PyDict_Type, + &PySet_Type, + &PyMemoryView_Type, + &PyByteArray_Type, + &PyGetSetDescr_Type, + &PyMemberDescr_Type, + &PyProperty_Type, + &PyWrapperDescr_Type, + &PyMethodDescr_Type, + &PyClassMethod_Type, // TODO(Immutable): mjp I added this, is it correct? Discuss with maj + &PyClassMethodDescr_Type, + &PyStaticMethod_Type, + &PyMethod_Type, + &PyCapsule_Type, + &PyCode_Type, + &PyCell_Type, + &PyFrame_Type, + &_PyWeakref_RefType, + &PyModule_Type, // TODO(Immutable): mjp I added this, is it correct? Discuss with maj + &_PyImmModule_Type, + NULL + }; + for (int i = 0; builtin_freezable_types[i] != NULL; i++) { + if (_PyImmutability_SetFreezable(builtin_freezable_types[i], _Py_FREEZABLE_YES)) { + return -1; + } + } return 0; } @@ -1431,58 +1466,6 @@ static int freeze_visit(PyObject* obj, void* freeze_state_untyped) return 0; } - - -static bool -is_freezable_builtin(PyTypeObject *type) -{ - if( - type == &PyType_Type || - type == &PyBaseObject_Type || - type == &PyFunction_Type || - type == &_PyNone_Type || - type == &PyBool_Type || - type == &PyLong_Type || - type == &PyFloat_Type || - type == &PyComplex_Type || - type == &PyBytes_Type || - type == &PyUnicode_Type || - type == &PyTuple_Type || - type == &PyList_Type || - type == &PyDict_Type || - type == &PySet_Type || - type == &PyFrozenSet_Type || - type == &PyMemoryView_Type || - type == &PyByteArray_Type || - type == &PyRange_Type || - type == &PyGetSetDescr_Type || - type == &PyMemberDescr_Type || - type == &PyProperty_Type || - type == &PyWrapperDescr_Type || - type == &PyMethodDescr_Type || - type == &PyClassMethod_Type || // TODO(Immutable): mjp I added this, is it correct? Discuss with maj - type == &PyClassMethodDescr_Type || - type == &PyStaticMethod_Type || - type == &PyMethod_Type || - type == &PyCFunction_Type || - type == &PyCapsule_Type || - type == &PyCode_Type || - type == &PyCell_Type || - type == &PyFrame_Type || - type == &_PyWeakref_RefType || - type == &_PyNotImplemented_Type || // TODO(Immutable): mjp I added this, is it correct? Discuss with maj - type == &PyModule_Type || // TODO(Immutable): mjp I added this, is it correct? Discuss with maj - type == &_PyImmModule_Type || - type == &PyEllipsis_Type - ) - { - return true; - } - - return false; -} - - static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, struct FreezeState *freeze_state) { @@ -1507,10 +1490,6 @@ static int check_freezable(struct _Py_immutability_state *state, PyObject* obj, } } - if(is_freezable_builtin(obj->ob_type)){ - return 0; - } - // TODO(Immutable): Visit what the right balance of making Python types immutable is. if(!_PyType_HasExtensionSlots(obj->ob_type)){ return 0; From 557daed8627d37f2aeed29856f1e8a442938b77c Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 15:53:53 +0100 Subject: [PATCH 5/6] Immutability: More cleanup --- Lib/test/test_freeze/test_core.py | 2 +- Python/immutability.c | 17 +++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_freeze/test_core.py b/Lib/test/test_freeze/test_core.py index 402bd442804258..ec901726e79559 100644 --- a/Lib/test/test_freeze/test_core.py +++ b/Lib/test/test_freeze/test_core.py @@ -1,7 +1,7 @@ import unittest from immutable import freeze, is_frozen -from .test_common import BaseNotFreezableTest, BaseObjectTest +from .test_common import BaseObjectTest # This is a canary to check that global variables are not made immutable diff --git a/Python/immutability.c b/Python/immutability.c index 227a455d06b153..71fb1baf795d55 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -150,11 +150,7 @@ int init_state(struct _Py_immutability_state *state) NULL }; for (int i = 0; shallow_types[i] != NULL; i++) { - if (_Py_hashtable_set(state->shallow_immutable_types, - shallow_types[i], (void *)1) < 0) { - return -1; - } - if (_PyImmutability_SetFreezable(shallow_types[i], _Py_FREEZABLE_YES)) { + if (_PyImmutability_RegisterShallowImmutable(shallow_types[i])) { return -1; } } @@ -184,6 +180,8 @@ int init_state(struct _Py_immutability_state *state) &_PyWeakref_RefType, &PyModule_Type, // TODO(Immutable): mjp I added this, is it correct? Discuss with maj &_PyImmModule_Type, + &PyCFunction_Type, + &_PyMethodWrapper_Type, NULL }; for (int i = 0; builtin_freezable_types[i] != NULL; i++) { @@ -1648,6 +1646,11 @@ int _PyImmutability_RegisterShallowImmutable(PyTypeObject* tp) PyErr_NoMemory(); return -1; } + + // Mark the type also as freezable + if (_PyImmutability_SetFreezable(tp, _Py_FREEZABLE_YES)) { + return -1; + } return 0; } @@ -2111,10 +2114,8 @@ static int traverse_freeze(PyObject* obj, struct FreezeState* freeze_state) debug_obj("Traversing %s (%p) rc=%zd\n", obj, Py_REFCNT(obj)); if(is_c_wrapper(obj)) { + // FIXME(Immutable): Is this still needed? set_direct_rc(obj); - // C functions are not mutable - // Types are manually traversed - return 0; } SUCCEEDS(get_reachable_proc(Py_TYPE(obj))(obj, (visitproc)freeze_visit, freeze_state)); From d31e5617cd6f656011f41b99daa84a740170f506 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sun, 15 Mar 2026 16:20:20 +0100 Subject: [PATCH 6/6] sudo `CI=green` --- Include/internal/pycore_immutability.h | 1 - Lib/test/test_freeze/test_bz2.py | 8 ++-- Lib/test/test_freeze/test_common.py | 9 ++-- Lib/test/test_freeze/test_csv.py | 8 ++-- Lib/test/test_freeze/test_etree.py | 4 +- Lib/test/test_freeze/test_io.py | 34 +++++++--------- Lib/test/test_freeze/test_multiprocessing.py | 4 +- Modules/_collectionsmodule.c | 4 +- Modules/_datetimemodule.c | 2 +- Modules/_decimal/_decimal.c | 6 +-- Modules/_struct.c | 2 +- Python/immutability.c | 43 ++++++++------------ Python/pystate.c | 1 - 13 files changed, 54 insertions(+), 72 deletions(-) diff --git a/Include/internal/pycore_immutability.h b/Include/internal/pycore_immutability.h index be8c1d51592b64..2719eb88e8d9cf 100644 --- a/Include/internal/pycore_immutability.h +++ b/Include/internal/pycore_immutability.h @@ -13,7 +13,6 @@ typedef struct _Py_hashtable_t _Py_hashtable_t; struct _Py_immutability_state { int late_init_done; _Py_hashtable_t *shallow_immutable_types; - PyObject *destroy_cb; _Py_hashtable_t *warned_types; // With the pre-freeze hook it can happen that freeze calls are // nested. This is stack of the enclosing freeze states. diff --git a/Lib/test/test_freeze/test_bz2.py b/Lib/test/test_freeze/test_bz2.py index d7530eef06eee0..aea6f14784df88 100644 --- a/Lib/test/test_freeze/test_bz2.py +++ b/Lib/test/test_freeze/test_bz2.py @@ -8,13 +8,13 @@ class TestBZ2Compressor(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=BZ2Compressor(), **kwargs) + def test_not_freezable(self): + self.check_not_freezable(BZ2Compressor()) class TestBZ2Decompressor(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=BZ2Decompressor(), **kwargs) + def test_not_freezable(self): + self.check_not_freezable(BZ2Decompressor()) if __name__ == '__main__': diff --git a/Lib/test/test_freeze/test_common.py b/Lib/test/test_freeze/test_common.py index e10161ea4fe9dd..275be9e4979eb0 100644 --- a/Lib/test/test_freeze/test_common.py +++ b/Lib/test/test_freeze/test_common.py @@ -30,14 +30,13 @@ def __init__(self, *args, obj=None, **kwargs): unittest.TestCase.__init__(self, *args, **kwargs) self.obj = obj - def test_not_freezable(self): - if self.obj == None: - return + def check_not_freezable(self, obj): + self.assertIsNotNone(obj) with self.assertRaises(TypeError): - freeze(self.obj) + freeze(obj) - self.assertFalse(is_frozen(self.obj)) + self.assertFalse(is_frozen(obj)) if __name__ == '__main__': diff --git a/Lib/test/test_freeze/test_csv.py b/Lib/test/test_freeze/test_csv.py index e04ada5401fbf0..f133a5df9b8903 100644 --- a/Lib/test/test_freeze/test_csv.py +++ b/Lib/test/test_freeze/test_csv.py @@ -5,11 +5,11 @@ class TestCSVReader(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=csv.reader([]), **kwargs) + def test_not_freezable(self): + self.check_not_freezable(csv.reader([])) class TestCSVWriter(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): + def test_not_freezable(self): self.buffer = BytesIO() - super().__init__(*args, obj=csv.writer(self.buffer), **kwargs) + self.check_not_freezable(csv.writer(self.buffer)) diff --git a/Lib/test/test_freeze/test_etree.py b/Lib/test/test_freeze/test_etree.py index 511339e6f2c578..18d998806a1866 100644 --- a/Lib/test/test_freeze/test_etree.py +++ b/Lib/test/test_freeze/test_etree.py @@ -10,8 +10,8 @@ # super().__init__(*args, obj=ElementTree(), **kwargs) class TestXMLParser(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=XMLParser(), **kwargs) + def test_not_freezable(self): + self.check_not_freezable(XMLParser()) class TestElement(BaseObjectTest): diff --git a/Lib/test/test_freeze/test_io.py b/Lib/test/test_freeze/test_io.py index d7c0ac950f034d..ac75b52e233583 100644 --- a/Lib/test/test_freeze/test_io.py +++ b/Lib/test/test_freeze/test_io.py @@ -4,34 +4,28 @@ class BytesIOTest(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=io.BytesIO(), **kwargs) - - def tearDown(self): - self.obj.close() + def test_not_freezable(self): + obj = io.BytesIO() + self.check_not_freezable(obj) + obj.close() class StringIOTest(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=io.StringIO(), **kwargs) - - def tearDown(self): - self.obj.close() + def test_not_freezable(self): + obj = io.StringIO() + self.check_not_freezable(obj) + obj.close() class TextWrapperTest(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): + def test_not_freezable(self): handle = open(__file__, 'r') - super().__init__(*args, obj=handle, **kwargs) - - def tearDown(self): - self.obj.close() + self.check_not_freezable(handle) + handle.close() class RawWrapperTest(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): + def test_not_freezable(self): handle = open(__file__, 'rb') - super().__init__(*args, obj=handle, **kwargs) - - def tearDown(self): - self.obj.close() + self.check_not_freezable(handle) + handle.close() diff --git a/Lib/test/test_freeze/test_multiprocessing.py b/Lib/test/test_freeze/test_multiprocessing.py index 9cce1d2377f756..e4947dcfc313a6 100644 --- a/Lib/test/test_freeze/test_multiprocessing.py +++ b/Lib/test/test_freeze/test_multiprocessing.py @@ -6,5 +6,5 @@ SEM_VALUE_MAX = SemLock.SEM_VALUE_MAX class TestSemLock(BaseNotFreezableTest): - def __init__(self, *args, **kwargs): - super().__init__(*args, obj=SemLock(SEMAPHORE, 0, SEM_VALUE_MAX, "mock", True), **kwargs) + def test_not_freezable(self): + self.check_not_freezable(SemLock(SEMAPHORE, 0, SEM_VALUE_MAX, "mock", True)) diff --git a/Modules/_collectionsmodule.c b/Modules/_collectionsmodule.c index 136adebd8f5168..0749ae3b01e23c 100644 --- a/Modules/_collectionsmodule.c +++ b/Modules/_collectionsmodule.c @@ -2904,11 +2904,11 @@ collections_exec(PyObject *module) { ADD_TYPE(module, &dequereviter_spec, state->dequereviter_type, NULL); ADD_TYPE(module, &tuplegetter_spec, state->tuplegetter_type, NULL); - if(_PyImmutability_SetFreezable(state->deque_type, _Py_FREEZABLE_YES) < 0){ + if(_PyImmutability_SetFreezable((PyObject*)state->deque_type, _Py_FREEZABLE_YES) < 0){ return -1; } - if(_PyImmutability_SetFreezable(state->defdict_type, _Py_FREEZABLE_YES) < 0){ + if(_PyImmutability_SetFreezable((PyObject*)state->defdict_type, _Py_FREEZABLE_YES) < 0){ return -1; } diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index eb4958f0c7ce1f..86c575250fdae6 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -7498,7 +7498,7 @@ _PyDateTime_InitTypes(PyInterpreterState *interp) } // TODO(Immutable): Revisit after PLDI deadline. - if(_PyImmutability_SetFreezable(capi_types[i], _Py_FREEZABLE_YES) < 0) { + if(_PyImmutability_SetFreezable((PyObject*)capi_types[i], _Py_FREEZABLE_YES) < 0) { return _PyStatus_ERR("could not freeze static types"); } } diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index df0efc21c3567e..c77b755255b1c7 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7772,9 +7772,9 @@ _decimal_exec(PyObject *m) CHECK_INT(PyModule_AddType(m, state->PyDecContext_Type)); CHECK_INT(PyModule_AddType(m, state->DecimalTuple)); - CHECK_INT(_PyImmutability_SetFreezable(state->PyDec_Type, _Py_FREEZABLE_YES)); - CHECK_INT(_PyImmutability_SetFreezable(state->PyDecContext_Type, _Py_FREEZABLE_YES)); - CHECK_INT(_PyImmutability_SetFreezable(state->PyDecSignalDict_Type, _Py_FREEZABLE_YES)); + CHECK_INT(_PyImmutability_SetFreezable((PyObject*)state->PyDec_Type, _Py_FREEZABLE_YES)); + CHECK_INT(_PyImmutability_SetFreezable((PyObject*)state->PyDecContext_Type, _Py_FREEZABLE_YES)); + CHECK_INT(_PyImmutability_SetFreezable((PyObject*)state->PyDecSignalDict_Type, _Py_FREEZABLE_YES)); /* Create top level exception */ ASSIGN_PTR(state->DecimalException, PyErr_NewException( diff --git a/Modules/_struct.c b/Modules/_struct.c index 91961ada84e7a6..efeb8eac4c8a34 100644 --- a/Modules/_struct.c +++ b/Modules/_struct.c @@ -2705,7 +2705,7 @@ _structmodule_exec(PyObject *m) if (PyModule_AddType(m, (PyTypeObject *)state->PyStructType) < 0) { return -1; } - if (_PyImmutability_SetFreezable(state->PyStructType, _Py_FREEZABLE_YES) < 0){ + if (_PyImmutability_SetFreezable((PyObject*)state->PyStructType, _Py_FREEZABLE_YES) < 0){ return -1; } diff --git a/Python/immutability.c b/Python/immutability.c index 71fb1baf795d55..f449bfadf78bda 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -95,23 +95,6 @@ // Macro that jumps to error, if the expression `x` does not succeed. #define SUCCEEDS(x) { do { int r = (x); if (r != 0) goto error; } while (0); } -static PyObject * -_destroy(PyObject* set, PyObject *objweakref) -{ - Py_INCREF(set); - if (PySet_Discard(set, objweakref) < 0) { - Py_DECREF(set); - return NULL; - } - Py_DECREF(set); - - Py_RETURN_NONE; -} - -static PyMethodDef _destroy_def = { - "_destroy", (PyCFunction) _destroy, METH_O -}; - static int init_state(struct _Py_immutability_state *state) { @@ -126,6 +109,8 @@ int init_state(struct _Py_immutability_state *state) _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct); if(state->shallow_immutable_types == NULL){ + _Py_hashtable_destroy(state->warned_types); + state->warned_types = NULL; return -1; } @@ -185,7 +170,7 @@ int init_state(struct _Py_immutability_state *state) NULL }; for (int i = 0; builtin_freezable_types[i] != NULL; i++) { - if (_PyImmutability_SetFreezable(builtin_freezable_types[i], _Py_FREEZABLE_YES)) { + if (_PyImmutability_SetFreezable((PyObject*)builtin_freezable_types[i], _Py_FREEZABLE_YES)) { return -1; } } @@ -1523,19 +1508,25 @@ int _PyImmutability_SetFreezable(PyObject *obj, _Py_freezable_status status) return -1; } - assert(PyErr_Occurred() == NULL); int rc = PyObject_SetAttr(obj, &_Py_ID(__freezable__), value); Py_DECREF(value); if (rc == 0) { return 0; - } else { - // Attempting to set the attribute can cause different errors - // depending on the type implementation. We just clear it here - // and store the freezability in the object fields instead. - // This should be safe, since `PyErr` should never be set when - // this function is called. + } + + // If setting the attribute failed, only fall back to ob_flags for + // "attribute not supported / read-only" cases. Propagate all other + // exceptions to the caller. + if (PyErr_ExceptionMatches(PyExc_AttributeError) || + PyErr_ExceptionMatches(PyExc_TypeError)) + { PyErr_Clear(); } + else { + // Preserve the original error (e.g. MemoryError or a custom + // tp_setattro exception). + return -1; + } // If the object doesn't support attribute setting, fall back // to ob_flags (64-bit only). @@ -1648,7 +1639,7 @@ int _PyImmutability_RegisterShallowImmutable(PyTypeObject* tp) } // Mark the type also as freezable - if (_PyImmutability_SetFreezable(tp, _Py_FREEZABLE_YES)) { + if (_PyImmutability_SetFreezable((PyObject*)tp, _Py_FREEZABLE_YES)) { return -1; } return 0; diff --git a/Python/pystate.c b/Python/pystate.c index 0fef8e4b138008..5a10b241fd27b6 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -792,7 +792,6 @@ interpreter_clear(PyInterpreterState *interp, PyThreadState *tstate) assert(interp->imports.importlib == NULL); assert(interp->imports.import_func == NULL); - Py_CLEAR(interp->immutability.destroy_cb); if (interp->immutability.warned_types != NULL) { _Py_hashtable_destroy(interp->immutability.warned_types); interp->immutability.warned_types = NULL;