[Python-checkins] gh-95388: Deprecate creating immutable types with mutable bases (GH-95533)

encukou webhook-mailer at python.org
Thu Aug 4 10:13:50 EDT 2022


https://github.com/python/cpython/commit/a613fedd6e18e4ab382cf81ec767e1135fc949a7
commit: a613fedd6e18e4ab382cf81ec767e1135fc949a7
branch: main
author: Petr Viktorin <encukou at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2022-08-04T16:13:45+02:00
summary:

gh-95388: Deprecate creating immutable types with mutable bases (GH-95533)

files:
A Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst
M Doc/whatsnew/3.12.rst
M Lib/test/test_capi.py
M Modules/_testcapi/heaptype.c
M Objects/typeobject.c

diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index 67396f8e0228..be0599763e22 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -205,6 +205,10 @@ Pending Removal in Python 3.14
 
   (Contributed by Jason R. Coombs and Hugo van Kemenade in :gh:`93963`.)
 
+* Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
+  bases using the C API.
+
+
 Pending Removal in Future Versions
 ----------------------------------
 
@@ -458,6 +462,9 @@ Deprecated
   :c:type:`PyConfig` instead.
   (Contributed by Victor Stinner in :gh:`77782`.)
 
+* Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
+  bases is deprecated and will be disabled in Python 3.14.
+
 
 Removed
 -------
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 013229a6cdc9..c74355789747 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -15,6 +15,7 @@
 import threading
 import time
 import unittest
+import warnings
 import weakref
 from test import support
 from test.support import MISSING_C_DOCSTRINGS
@@ -644,6 +645,34 @@ def test_pytype_fromspec_with_repeated_slots(self):
                 with self.assertRaises(SystemError):
                     _testcapi.create_type_from_repeated_slots(variant)
 
+    def test_immutable_type_with_mutable_base(self):
+        # Add deprecation warning here so it's removed in 3.14
+        warnings._deprecated(
+            'creating immutable classes with mutable bases', remove=(3, 14))
+
+        class MutableBase:
+            def meth(self):
+                return 'original'
+
+        with self.assertWarns(DeprecationWarning):
+            ImmutableSubclass = _testcapi.make_immutable_type_with_base(
+                MutableBase)
+        instance = ImmutableSubclass()
+
+        self.assertEqual(instance.meth(), 'original')
+
+        # Cannot override the static type's method
+        with self.assertRaisesRegex(
+                TypeError,
+                "cannot set 'meth' attribute of immutable type"):
+            ImmutableSubclass.meth = lambda self: 'overridden'
+        self.assertEqual(instance.meth(), 'original')
+
+        # Can change the method on the mutable base
+        MutableBase.meth = lambda self: 'changed'
+        self.assertEqual(instance.meth(), 'changed')
+
+
     def test_pynumber_tobase(self):
         from _testcapi import pynumber_tobase
         self.assertEqual(pynumber_tobase(123, 2), '0b1111011')
diff --git a/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst b/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst
new file mode 100644
index 000000000000..c389d13db6af
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-07-29-10-41-59.gh-issue-95388.aiRSgr.rst	
@@ -0,0 +1,2 @@
+Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
+bases is deprecated and is planned to be disabled in Python 3.14.
diff --git a/Modules/_testcapi/heaptype.c b/Modules/_testcapi/heaptype.c
index 12889e825d55..514541ce348d 100644
--- a/Modules/_testcapi/heaptype.c
+++ b/Modules/_testcapi/heaptype.c
@@ -365,6 +365,21 @@ create_type_from_repeated_slots(PyObject *self, PyObject *variant_obj)
 }
 
 
+
+static PyObject *
+make_immutable_type_with_base(PyObject *self, PyObject *base)
+{
+    assert(PyType_Check(base));
+    PyType_Spec ImmutableSubclass_spec = {
+        .name = "ImmutableSubclass",
+        .basicsize = (int)((PyTypeObject*)base)->tp_basicsize,
+        .slots = empty_type_slots,
+        .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_IMMUTABLETYPE,
+    };
+    return PyType_FromSpecWithBases(&ImmutableSubclass_spec, base);
+}
+
+
 static PyMethodDef TestMethods[] = {
     {"pytype_fromspec_meta",    pytype_fromspec_meta,            METH_O},
     {"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS},
@@ -375,6 +390,7 @@ static PyMethodDef TestMethods[] = {
     {"test_from_spec_invalid_metatype_inheritance",
      test_from_spec_invalid_metatype_inheritance,
      METH_NOARGS},
+    {"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
     {NULL},
 };
 
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 1980fcbf9323..0801d9f3d5f1 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -3676,6 +3676,32 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
         goto finally;
     }
 
+    /* If this is an immutable type, check if all bases are also immutable,
+     * and (for now) fire a deprecation warning if not.
+     * (This isn't necessary for static types: those can't have heap bases,
+     * 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)) {
+                if (PyErr_WarnFormat(
+                    PyExc_DeprecationWarning,
+                    0,
+                    "Creating immutable type %s from mutable base %s is "
+                    "deprecated, and slated to be disallowed in Python 3.14.",
+                    spec->name,
+                    b->tp_name))
+                {
+                    goto finally;
+                }
+            }
+        }
+    }
+
     /* Calculate the metaclass */
 
     if (!metaclass) {



More information about the Python-checkins mailing list