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

This commit is contained in:
Petr Viktorin 2022-08-04 16:13:45 +02:00 committed by GitHub
parent 000c3874bf
commit a613fedd6e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 0 deletions

View File

@ -205,6 +205,10 @@ Pending Removal in Python 3.14
(Contributed by Jason R. Coombs and Hugo van Kemenade in :gh:`93963`.) (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 Pending Removal in Future Versions
---------------------------------- ----------------------------------
@ -458,6 +462,9 @@ Deprecated
:c:type:`PyConfig` instead. :c:type:`PyConfig` instead.
(Contributed by Victor Stinner in :gh:`77782`.) (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 Removed
------- -------

View File

@ -15,6 +15,7 @@ import textwrap
import threading import threading
import time import time
import unittest import unittest
import warnings
import weakref import weakref
from test import support from test import support
from test.support import MISSING_C_DOCSTRINGS from test.support import MISSING_C_DOCSTRINGS
@ -644,6 +645,34 @@ class CAPITest(unittest.TestCase):
with self.assertRaises(SystemError): with self.assertRaises(SystemError):
_testcapi.create_type_from_repeated_slots(variant) _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): def test_pynumber_tobase(self):
from _testcapi import pynumber_tobase from _testcapi import pynumber_tobase
self.assertEqual(pynumber_tobase(123, 2), '0b1111011') self.assertEqual(pynumber_tobase(123, 2), '0b1111011')

View File

@ -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.

View File

@ -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[] = { static PyMethodDef TestMethods[] = {
{"pytype_fromspec_meta", pytype_fromspec_meta, METH_O}, {"pytype_fromspec_meta", pytype_fromspec_meta, METH_O},
{"test_type_from_ephemeral_spec", test_type_from_ephemeral_spec, METH_NOARGS}, {"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",
test_from_spec_invalid_metatype_inheritance, test_from_spec_invalid_metatype_inheritance,
METH_NOARGS}, METH_NOARGS},
{"make_immutable_type_with_base", make_immutable_type_with_base, METH_O},
{NULL}, {NULL},
}; };

View File

@ -3676,6 +3676,32 @@ PyType_FromMetaclass(PyTypeObject *metaclass, PyObject *module,
goto finally; 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 */ /* Calculate the metaclass */
if (!metaclass) { if (!metaclass) {