FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

gh-121654: Add PyType_Freeze() function by vstinner · Pull Request #122457 · python/cpython · GitHub

/ cpython Public
14 changes: 14 additions & 0 deletions Doc/c-api/type.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,20 @@ The following functions and structs are used to create
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``.
Comment thread
vstinner marked this conversation as resolved.

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) -->
Expand Down
1 change: 1 addition & 0 deletions Doc/data/stable_abi.dat

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Doc/whatsnew/3.14.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,9 @@ New features
(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
----------------------

Expand Down
4 changes: 4 additions & 0 deletions Include/object.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,10 @@ static inline int PyType_CheckExact(PyObject *op) {
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
Expand Down
66 changes: 66 additions & 0 deletions Lib/test/test_capi/test_type.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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)
Comment thread
vstinner marked this conversation as resolved.

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)
1 change: 1 addition & 0 deletions Lib/test/test_stable_abi_ctypes.py

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyType_Freeze` function to make a type immutable. Patch by
Victor Stinner.
2 changes: 2 additions & 0 deletions Misc/stable_abi.toml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -2538,3 +2538,5 @@
added = '3.14'
[function.PyUnicode_Equal]
added = '3.14'
[function.PyType_Freeze]
added = '3.14'
16 changes: 16 additions & 0 deletions Modules/_testcapimodule.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3310,6 +3310,7 @@ test_critical_sections(PyObject *module, PyObject *Py_UNUSED(args))
Py_RETURN_NONE;
}


// Used by `finalize_thread_hang`.
#ifdef _POSIX_THREADS
static void finalize_thread_hang_cleanup_callback(void *Py_UNUSED(arg)) {
Expand Down Expand Up @@ -3339,6 +3340,20 @@ finalize_thread_hang(PyObject *self, PyObject *callback)
}


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},
Expand Down Expand Up @@ -3479,6 +3494,7 @@ static PyMethodDef TestMethods[] = {
{"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 */
};

Expand Down
65 changes: 52 additions & 13 deletions Objects/typeobject.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -4637,6 +4637,32 @@ check_basicsize_includes_size_and_offsets(PyTypeObject* type)
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Sorry for the necro-posting.
IIUC only improperly constructed tuples can store NULL items. Is this such a case? If so, is the following check redundant?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I don't know, I just moved the code. The test was already there before.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

Got it, thanks!

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.
*/
Expand Down Expand Up @@ -4820,19 +4846,8 @@ PyType_FromMetaclass(
* 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;
}
}

Expand Down Expand Up @@ -11319,6 +11334,30 @@ add_operators(PyTypeObject *type)
}


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;
}
Comment on lines +11342 to +11346

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

How difficult to add test for this case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Quality

I'm not sure that it's possible to create a type with a MRO which is not a tuple. Using a metaclass, it's possible to override the MRO, but internally, Python converts mro() result into a tuple.

I added this check since type_get_mro() returns None if type->tp_mro is NULL. But I have no idea how to create a type with type->tp_mro=NULL. It's more a sanity check.


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 {
Expand Down
1 change: 1 addition & 0 deletions PC/python3dll.c

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.


Back | FazBrowse Home | New Git URL