Creating classes whose metaclass overrides
:c:member:`~PyTypeObject.tp_new` is no longer allowed.
+.. c:function:: int PyType_Freeze(PyTypeObject *type)
+
+ Make a type immutable: set the :c:macro:`Py_TPFLAGS_IMMUTABLETYPE` flag.
+
+ All base classes of *type* must be immutable.
+
+ On success, return ``0``.
+ On error, set an exception and return ``-1``.
+
+ The type must not be used before it's made immutable. For example, type
+ instances must not be created before the type is made immutable.
+
+ .. versionadded:: 3.14
+
.. raw:: html
<!-- Keep old URL fragments working (see gh-97908) -->
data,PyTuple_Type,3.2,,
type,PyTypeObject,3.2,,opaque
func,PyType_ClearCache,3.2,,
+func,PyType_Freeze,3.14,,
func,PyType_FromMetaclass,3.12,,
func,PyType_FromModuleAndSpec,3.10,,
func,PyType_FromSpec,3.2,,
(Contributed by Victor Stinner in :gh:`124502`.)
+* Add :c:func:`PyType_Freeze` function to make a type immutable.
+ (Contributed by Victor Stinner in :gh:`121654`.)
+
Porting to Python 3.14
----------------------
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
#endif
+#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030e0000
+PyAPI_FUNC(int) PyType_Freeze(PyTypeObject *type);
+#endif
+
#ifdef __cplusplus
}
#endif
--- /dev/null
+from test.support import import_helper
+import unittest
+
+_testcapi = import_helper.import_module('_testcapi')
+
+
+class TypeTests(unittest.TestCase):
+ def test_freeze(self):
+ # test PyType_Freeze()
+ type_freeze = _testcapi.type_freeze
+
+ # simple case, no inherante
+ class MyType:
+ pass
+ MyType.attr = "mutable"
+
+ type_freeze(MyType)
+ err_msg = "cannot set 'attr' attribute of immutable type 'MyType'"
+ with self.assertRaisesRegex(TypeError, err_msg):
+ # the class is now immutable
+ MyType.attr = "immutable"
+
+ # test MRO: PyType_Freeze() requires base classes to be immutable
+ class A: pass
+ class B: pass
+ class C(B): pass
+ class D(A, C): pass
+
+ self.assertEqual(D.mro(), [D, A, C, B, object])
+ with self.assertRaises(TypeError):
+ type_freeze(D)
+
+ type_freeze(A)
+ type_freeze(B)
+ type_freeze(C)
+ # all parent classes are now immutable, so D can be made immutable
+ # as well
+ type_freeze(D)
+
+ def test_freeze_meta(self):
+ """test PyType_Freeze() with overridden MRO"""
+ type_freeze = _testcapi.type_freeze
+
+ class Base:
+ value = 1
+
+ class Meta(type):
+ def mro(cls):
+ return (cls, Base, object)
+
+ class FreezeThis(metaclass=Meta):
+ """This has `Base` in the MRO, but not tp_bases"""
+
+ self.assertEqual(FreezeThis.value, 1)
+
+ with self.assertRaises(TypeError):
+ type_freeze(FreezeThis)
+
+ Base.value = 2
+ self.assertEqual(FreezeThis.value, 2)
+
+ type_freeze(Base)
+ with self.assertRaises(TypeError):
+ Base.value = 3
+ type_freeze(FreezeThis)
+ self.assertEqual(FreezeThis.value, 2)
"PyTuple_Size",
"PyTuple_Type",
"PyType_ClearCache",
+ "PyType_Freeze",
"PyType_FromMetaclass",
"PyType_FromModuleAndSpec",
"PyType_FromSpec",
--- /dev/null
+Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
+Victor Stinner.
added = '3.14'
[function.PyUnicode_Equal]
added = '3.14'
+[function.PyType_Freeze]
+ added = '3.14'
Py_RETURN_NONE;
}
+
// Used by `finalize_thread_hang`.
#ifdef _POSIX_THREADS
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
}
+static PyObject *
+type_freeze(PyObject *module, PyObject *args)
+{
+ PyTypeObject *type;
+ if (!PyArg_ParseTuple(args, "O!", &PyType_Type, &type)) {
+ return NULL;
+ }
+ if (PyType_Freeze(type) < 0) {
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+
static PyMethodDef TestMethods[] = {
{"set_errno", set_errno, METH_VARARGS},
{"test_config", test_config, METH_NOARGS},
{"function_set_warning", function_set_warning, METH_NOARGS},
{"test_critical_sections", test_critical_sections, METH_NOARGS},
{"finalize_thread_hang", finalize_thread_hang, METH_O, NULL},
+ {"type_freeze", type_freeze, METH_VARARGS},
{NULL, NULL} /* sentinel */
};
return 1;
}
+static int
+check_immutable_bases(const char *type_name, PyObject *bases, int skip_first)
+{
+ Py_ssize_t i = 0;
+ if (skip_first) {
+ // When testing the MRO, skip the type itself
+ i = 1;
+ }
+ for (; i<PyTuple_GET_SIZE(bases); i++) {
+ PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
+ if (!b) {
+ return -1;
+ }
+ if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
+ PyErr_Format(
+ PyExc_TypeError,
+ "Creating immutable type %s from mutable base %N",
+ type_name, b
+ );
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
/* Set *dest to the offset specified by a special "__*offset__" member.
* Return 0 on success, -1 on failure.
*/
* and only heap types can be mutable.)
*/
if (spec->flags & Py_TPFLAGS_IMMUTABLETYPE) {
- for (int i=0; i<PyTuple_GET_SIZE(bases); i++) {
- PyTypeObject *b = (PyTypeObject*)PyTuple_GET_ITEM(bases, i);
- if (!b) {
- goto finally;
- }
- if (!_PyType_HasFeature(b, Py_TPFLAGS_IMMUTABLETYPE)) {
- PyErr_Format(
- PyExc_TypeError,
- "Creating immutable type %s from mutable base %N",
- spec->name, b
- );
- goto finally;
- }
+ if (check_immutable_bases(spec->name, bases, 0) < 0) {
+ goto finally;
}
}
}
+int
+PyType_Freeze(PyTypeObject *type)
+{
+ // gh-121654: Check the __mro__ instead of __bases__
+ PyObject *mro = type_get_mro(type, NULL);
+ if (!PyTuple_Check(mro)) {
+ Py_DECREF(mro);
+ PyErr_SetString(PyExc_TypeError, "unable to get the type MRO");
+ return -1;
+ }
+
+ int check = check_immutable_bases(type->tp_name, mro, 1);
+ Py_DECREF(mro);
+ if (check < 0) {
+ return -1;
+ }
+
+ type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
+ PyType_Modified(type);
+
+ return 0;
+}
+
+
/* Cooperative 'super' */
typedef struct {
EXPORT_FUNC(PyTuple_SetItem)
EXPORT_FUNC(PyTuple_Size)
EXPORT_FUNC(PyType_ClearCache)
+EXPORT_FUNC(PyType_Freeze)
EXPORT_FUNC(PyType_FromMetaclass)
EXPORT_FUNC(PyType_FromModuleAndSpec)
EXPORT_FUNC(PyType_FromSpec)