mirror of https://github.com/python/cpython
gh-95388: Deprecate creating immutable types with mutable bases (GH-95533)
This commit is contained in:
parent
000c3874bf
commit
a613fedd6e
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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.
|
|
@ -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},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue