From 49f5e7571e89eb3a4b73e83fbe057c7420c735d8 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Jun 2023 14:16:05 -0600 Subject: [PATCH 1/9] Add PyType_GetDict(). --- Include/cpython/object.h | 1 + Objects/typeobject.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 7d69231aaa3119..7028177287bf85 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -283,6 +283,7 @@ PyAPI_FUNC(PyTypeObject *) _PyType_CalculateMetaclass(PyTypeObject *, PyObject * PyAPI_FUNC(PyObject *) _PyType_GetDocFromInternalDoc(const char *, const char *); PyAPI_FUNC(PyObject *) _PyType_GetTextSignatureFromInternalDoc(const char *, const char *); PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *); +PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *); PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int); PyAPI_FUNC(void) _Py_BreakPoint(void); diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1e0d79bc126452..988836824d1eab 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -238,6 +238,13 @@ _PyType_GetDict(PyTypeObject *self) return lookup_tp_dict(self); } +PyObject * +PyType_GetDict(PyTypeObject *self) +{ + PyObject *dict = lookup_tp_dict(self); + return _Py_XNewRef(dict); +} + static inline void set_tp_dict(PyTypeObject *self, PyObject *dict) { From 60d611e257b895ab6e2e654603ae9604c17ac83a Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Jun 2023 14:19:34 -0600 Subject: [PATCH 2/9] Add docs. --- Doc/c-api/type.rst | 16 ++++++++++++++++ Doc/c-api/typeobj.rst | 17 +++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index c99c7ef93a45df..ed8e45abbf9926 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -50,6 +50,22 @@ Type Objects The return type is now ``unsigned long`` rather than ``long``. +.. c:function:: PyObject* PyType_GetDict(PyTypeObject* type) + + Return the type object's internal namespace, which is otherwise only + exposed via a read-only proxy (``cls.__dict__``). This is a + replacement for accessing :c:member:`~PyTypeObject.tp_dict` directly. + The returned dictionary must be treated as read-only. + + This function isn't intended for general use. It's meant for + specific embgedding and language-binding cases, where direct access + to the dict is necessary and indirect access (e.g. via the proxy) + isn't adequate. Extension modules may continue to use ``tp_dict``, + directly or indirectly, when setting up their own types. + + .. versionadded:: 3.12 + + .. c:function:: void PyType_Modified(PyTypeObject *type) Invalidate the internal lookup cache for the type and all of its diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index c6e783acdf0654..46015bc108ddbe 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -110,7 +110,7 @@ Quick Reference +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ | :c:member:`~PyTypeObject.tp_base` | :c:type:`PyTypeObject` * | __base__ | | | X | | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ - | :c:member:`~PyTypeObject.tp_dict` | :c:type:`PyObject` * | __dict__ | | | ? | | + | <<:c:member:`~PyTypeObject.tp_dict`>> | :c:type:`PyObject` * | __dict__ | | | ? | | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ | :c:member:`~PyTypeObject.tp_descr_get` | :c:type:`descrgetfunc` | __get__ | | | | X | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ @@ -157,6 +157,9 @@ Quick Reference **<>**: Names in angle brackets should be initially set to ``NULL`` and treated as read-only. + **<<>>**: Names in double angle brackets should be initially set to + ``NULL`` and treated as read-only after initialization. + **[]**: Names in square brackets are for internal use only. **** (as a prefix) means the field is required (must be non-``NULL``). @@ -1717,7 +1720,17 @@ and :c:type:`PyType_Type` effectively act as defaults.) called; it may also be initialized to a dictionary containing initial attributes for the type. Once :c:func:`PyType_Ready` has initialized the type, extra attributes for the type may be added to this dictionary only if they don't - correspond to overloaded operations (like :meth:`__add__`). + correspond to overloaded operations (like :meth:`__add__`). Once + initialization for the type has finished, this field should be + treated as read-only. + + .. versionchanged:: 3.12 + + Internals detail: For the static builtin types this is always ``NULL``. + Instead, the dict for each is stored on ``PyInterpreterState``. + If needed, use :c:func:`PyType_GetDict` to get the corresponding + dict for those types. This is not normally necessary, + and certainly not for user-defined type objects. **Inheritance:** From 718bbb6bb47ff1d59b5bf7dc354e7f02cfddddf9 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Jun 2023 14:25:04 -0600 Subject: [PATCH 3/9] Add a NEWS entry. --- .../C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst diff --git a/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst b/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst new file mode 100644 index 00000000000000..6e0e5396f6844d --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst @@ -0,0 +1,5 @@ +The new :c:func:`PyType_GetDict` provides the dictionary for the given type +object that is normally exposed by ``cls.__dict__``. Normally it's +sufficient to use :c:member:`~PyTypeObject.tp_dict`, but for the static +builtin types ``tp_dict`` is now always ``NULL``. ``PyType_GetDict()`` +provides the correct dict object instead. From 269738d68888783c2f714ec8e98eeb3959e76a07 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Tue, 13 Jun 2023 15:05:02 -0600 Subject: [PATCH 4/9] Fix a typo. --- Doc/c-api/type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index ed8e45abbf9926..8f883f5299f672 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -58,7 +58,7 @@ Type Objects The returned dictionary must be treated as read-only. This function isn't intended for general use. It's meant for - specific embgedding and language-binding cases, where direct access + specific embedding and language-binding cases, where direct access to the dict is necessary and indirect access (e.g. via the proxy) isn't adequate. Extension modules may continue to use ``tp_dict``, directly or indirectly, when setting up their own types. From ac9a0719f6666a8d4c48b6d13ac81be5100d8703 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 10 Jul 2023 17:14:03 +0200 Subject: [PATCH 5/9] Docs: Mark this for general use (better than reading tp_dict directly!) --- Doc/c-api/type.rst | 9 +++++---- Doc/c-api/typeobj.rst | 12 +++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst index 8f883f5299f672..a5f333e2a31e03 100644 --- a/Doc/c-api/type.rst +++ b/Doc/c-api/type.rst @@ -57,10 +57,11 @@ Type Objects replacement for accessing :c:member:`~PyTypeObject.tp_dict` directly. The returned dictionary must be treated as read-only. - This function isn't intended for general use. It's meant for - specific embedding and language-binding cases, where direct access - to the dict is necessary and indirect access (e.g. via the proxy) - isn't adequate. Extension modules may continue to use ``tp_dict``, + This function is meant for specific embedding and language-binding cases, + where direct access to the dict is necessary and indirect access + (e.g. via the proxy or :c:func:`PyObject_GetAttr`) isn't adequate. + + Extension modules should continue to use ``tp_dict``, directly or indirectly, when setting up their own types. .. versionadded:: 3.12 diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 46015bc108ddbe..b3831b2dedec9c 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1724,13 +1724,15 @@ and :c:type:`PyType_Type` effectively act as defaults.) initialization for the type has finished, this field should be treated as read-only. + Some types may not store their dictionary in this slot. + Use :c:func:`PyType_GetDict` to retreive the dictionary for an arbitrary + type. + .. versionchanged:: 3.12 - Internals detail: For the static builtin types this is always ``NULL``. - Instead, the dict for each is stored on ``PyInterpreterState``. - If needed, use :c:func:`PyType_GetDict` to get the corresponding - dict for those types. This is not normally necessary, - and certainly not for user-defined type objects. + Internals detail: For static builtin types, this is always ``NULL``. + Instead, the dict for such types is stored on ``PyInterpreterState``. + Use :c:func:`PyType_GetDict` to get the dict for an arbitrary type. **Inheritance:** From 8368595e2a27d1ac01e5a75dc0edddbc6b9d13e9 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 10 Jul 2023 17:14:22 +0200 Subject: [PATCH 6/9] Linkify the NEWS entry --- .../next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst b/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst index 6e0e5396f6844d..846663621e8689 100644 --- a/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst +++ b/Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst @@ -1,5 +1,5 @@ The new :c:func:`PyType_GetDict` provides the dictionary for the given type object that is normally exposed by ``cls.__dict__``. Normally it's sufficient to use :c:member:`~PyTypeObject.tp_dict`, but for the static -builtin types ``tp_dict`` is now always ``NULL``. ``PyType_GetDict()`` +builtin types :c:member:`!tp_dict` is now always ``NULL``. :c:func:`!PyType_GetDict()` provides the correct dict object instead. From 23b3a41a8ff8e0c53c8849bc6f86041192e8a9b2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 10 Jul 2023 17:14:33 +0200 Subject: [PATCH 7/9] Add a rudimentary test --- Modules/_testcapimodule.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 35687b49f24a0d..8b4ed1924f10f2 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -640,6 +640,30 @@ test_get_type_qualname(PyObject *self, PyObject *Py_UNUSED(ignored)) Py_RETURN_NONE; } +static PyObject * +test_get_type_dict(PyObject *self, PyObject *Py_UNUSED(ignored)) +{ + /* Test for PyType_GetDict */ + + // Assert ints have a `to_bytes` method + PyObject *long_dict = PyType_GetDict(&PyLong_Type); + assert(long_dict); + assert(PyDict_GetItemString(long_dict, "to_bytes")); // borrowed ref + Py_DECREF(long_dict); + + // Make a new type, add an attribute to it and assert it's there + PyObject *HeapTypeNameType = PyType_FromSpec(&HeapTypeNameType_Spec); + assert(HeapTypeNameType); + assert(PyObject_SetAttrString( + HeapTypeNameType, "new_attr", Py_NewRef(Py_None)) >= 0); + PyObject *type_dict = PyType_GetDict((PyTypeObject*)HeapTypeNameType); + assert(type_dict); + assert(PyDict_GetItemString(type_dict, "new_attr")); // borrowed ref + Py_DECREF(HeapTypeNameType); + Py_DECREF(type_dict); + Py_RETURN_NONE; +} + static PyObject * pyobject_repr_from_null(PyObject *self, PyObject *Py_UNUSED(ignored)) { @@ -3357,6 +3381,7 @@ static PyMethodDef TestMethods[] = { {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS}, {"test_get_type_name", test_get_type_name, METH_NOARGS}, {"test_get_type_qualname", test_get_type_qualname, METH_NOARGS}, + {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, {"_test_thread_state", test_thread_state, METH_VARARGS}, #ifndef MS_WINDOWS {"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS}, From 7ed5d8661211c65f8a8dec397bd167bd7381fa57 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 10 Jul 2023 17:15:21 +0200 Subject: [PATCH 8/9] Docs: Revert the <<>> in the summary -- the info is in prose for now --- Doc/c-api/typeobj.rst | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index b3831b2dedec9c..9c8007bc4dc651 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -110,7 +110,7 @@ Quick Reference +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ | :c:member:`~PyTypeObject.tp_base` | :c:type:`PyTypeObject` * | __base__ | | | X | | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ - | <<:c:member:`~PyTypeObject.tp_dict`>> | :c:type:`PyObject` * | __dict__ | | | ? | | + | :c:member:`~PyTypeObject.tp_dict` | :c:type:`PyObject` * | __dict__ | | | ? | | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ | :c:member:`~PyTypeObject.tp_descr_get` | :c:type:`descrgetfunc` | __get__ | | | | X | +------------------------------------------------+-----------------------------------+-------------------+---+---+---+---+ @@ -157,9 +157,6 @@ Quick Reference **<>**: Names in angle brackets should be initially set to ``NULL`` and treated as read-only. - **<<>>**: Names in double angle brackets should be initially set to - ``NULL`` and treated as read-only after initialization. - **[]**: Names in square brackets are for internal use only. **** (as a prefix) means the field is required (must be non-``NULL``). From 97e1d9c624be0f0208fd75a3a8b015c127deb460 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Mon, 10 Jul 2023 17:39:00 +0200 Subject: [PATCH 9/9] Formatting nit --- Modules/_testcapimodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index bfda045369ff01..dd2c9c72e53787 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3496,7 +3496,7 @@ static PyMethodDef TestMethods[] = { {"test_get_statictype_slots", test_get_statictype_slots, METH_NOARGS}, {"test_get_type_name", test_get_type_name, METH_NOARGS}, {"test_get_type_qualname", test_get_type_qualname, METH_NOARGS}, - {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, + {"test_get_type_dict", test_get_type_dict, METH_NOARGS}, {"_test_thread_state", test_thread_state, METH_VARARGS}, #ifndef MS_WINDOWS {"_spawn_pthread_waiter", spawn_pthread_waiter, METH_NOARGS},