gh-121654: Add PyType_Freeze() function (#122457) · python/cpython@db96327 · GitHub
Skip to content

Commit db96327

Browse files
vstinnerencukou
andauthored
gh-121654: Add PyType_Freeze() function (#122457)
Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent da8673d commit db96327

11 files changed

Lines changed: 162 additions & 13 deletions

File tree

Doc/c-api/type.rst

Lines changed: 14 additions & 0 deletions

Doc/data/stable_abi.dat

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/whatsnew/3.14.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,9 @@ New features
777777
(Contributed by Victor Stinner in :gh:`124502`.)
778778

779779

780+
* Add :c:func:`PyType_Freeze` function to make a type immutable.
781+
(Contributed by Victor Stinner in :gh:`121654`.)
782+
780783
Porting to Python 3.14
781784
----------------------
782785

Include/object.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,10 @@ static inline int PyType_CheckExact(PyObject *op) {
796796
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
797797
#endif
798798

799+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
800+
PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
801+
#endif
802+
799803
#ifdef __cplusplus
800804
}
801805
#endif

Lib/test/test_capi/test_type.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from test.support import import_helper
2+
import unittest
3+
4+
_testcapi = import_helper.import_module('_testcapi')
5+
6+
7+
class TypeTests(unittest.TestCase):
8+
def test_freeze(self):
9+
# test PyType_Freeze()
10+
type_freeze = _testcapi.type_freeze
11+
12+
# simple case, no inherante
13+
class MyType:
14+
pass
15+
MyType.attr = "mutable"
16+
17+
type_freeze(MyType)
18+
err_msg = "cannot set 'attr' attribute of immutable type 'MyType'"
19+
with self.assertRaisesRegex(TypeError, err_msg):
20+
# the class is now immutable
21+
MyType.attr = "immutable"
22+
23+
# test MRO: PyType_Freeze() requires base classes to be immutable
24+
class A: pass
25+
class B: pass
26+
class C(B): pass
27+
class D(A, C): pass
28+
29+
self.assertEqual(D.mro(), [D, A, C, B, object])
30+
with self.assertRaises(TypeError):
31+
type_freeze(D)
32+
33+
type_freeze(A)
34+
type_freeze(B)
35+
type_freeze(C)
36+
# all parent classes are now immutable, so D can be made immutable
37+
# as well
38+
type_freeze(D)
39+
40+
def test_freeze_meta(self):
41+
"""test PyType_Freeze() with overridden MRO"""
42+
type_freeze = _testcapi.type_freeze
43+
44+
class Base:
45+
value = 1
46+
47+
class Meta(type):
48+
def mro(cls):
49+
return (cls, Base, object)
50+
51+
class FreezeThis(metaclass=Meta):
52+
"""This has `Base` in the MRO, but not tp_bases"""
53+
54+
self.assertEqual(FreezeThis.value, 1)
55+
56+
with self.assertRaises(TypeError):
57+
type_freeze(FreezeThis)
58+
59+
Base.value = 2
60+
self.assertEqual(FreezeThis.value, 2)
61+
62+
type_freeze(Base)
63+
with self.assertRaises(TypeError):
64+
Base.value = 3
65+
type_freeze(FreezeThis)
66+
self.assertEqual(FreezeThis.value, 2)

Lib/test/test_stable_abi_ctypes.py

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
2+
Victor Stinner.

Misc/stable_abi.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2538,3 +2538,5 @@
25382538
added = '3.14'
25392539
[function.PyUnicode_Equal]
25402540
added = '3.14'
2541+
[function.PyType_Freeze]
2542+
added = '3.14'

Modules/_testcapimodule.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3310,6 +3310,7 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
33103310
Py_RETURN_NONE;
33113311
}
33123312

3313+
33133314
// Used by `finalize_thread_hang`.
33143315
#ifdef _POSIX_THREADS
33153316
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
@@ -3339,6 +3340,20 @@ finalize_thread_hang(PyObject *self, PyObject *callback)
33393340
}
33403341

33413342

3343+
static PyObject *
3344+
type_freeze(PyObject *module, PyObject *args)
3345+
{
3346+
PyTypeObject *type;
3347+
if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &type)) {
3348+
return NULL;
3349+
}
3350+
if (PyType_Freeze(type) < 0) {
3351+
return NULL;
3352+
}
3353+
Py_RETURN_NONE;
3354+
}
3355+
3356+
33423357
static PyMethodDef TestMethods[] = {
33433358
{"set_errno", set_errno, METH_VARARGS},
33443359
{"test_config", test_config, METH_NOARGS},
@@ -3479,6 +3494,7 @@ static PyMethodDef TestMethods[] = {
34793494
{"function_set_warning", function_set_warning, METH_NOARGS},
34803495
{"test_critical_sections", test_critical_sections, METH_NOARGS},
34813496
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
3497+
{"type_freeze", type_freeze, METH_VARARGS},
34823498
{NULL, NULL} /* sentinel */
34833499
};
34843500

Objects/typeobject.c

Lines changed: 52 additions & 13 deletions

0 commit comments

Comments
 (0)