[Python-checkins] gh-105227: Add PyType_GetDict() (GH-105747)

encukou webhook-mailer at python.org
Mon Jul 10 12:41:07 EDT 2023


https://github.com/python/cpython/commit/a840806d338805fe74a9de01081d30da7605a29f
commit: a840806d338805fe74a9de01081d30da7605a29f
branch: main
author: Eric Snow <ericsnowcurrently at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2023-07-10T18:41:02+02:00
summary:

gh-105227: Add PyType_GetDict() (GH-105747)

This compensates for static builtin types having `tp_dict` set to `NULL`.

Co-authored-by: Petr Viktorin <encukou at gmail.com>

files:
A Misc/NEWS.d/next/C API/2023-06-13-14-24-55.gh-issue-105227.HDL9aF.rst
M Doc/c-api/type.rst
M Doc/c-api/typeobj.rst
M Include/cpython/object.h
M Modules/_testcapimodule.c
M Objects/typeobject.c

diff --git a/Doc/c-api/type.rst b/Doc/c-api/type.rst
index c99c7ef93a45d..a5f333e2a31e0 100644
--- a/Doc/c-api/type.rst
+++ b/Doc/c-api/type.rst
@@ -50,6 +50,23 @@ 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 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
+
+
 .. 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 239c191457f51..7249cfe79c32e 100644
--- a/Doc/c-api/typeobj.rst
+++ b/Doc/c-api/typeobj.rst
@@ -1717,7 +1717,19 @@ 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.
+
+   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 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:**
 
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index d681435c84545..ef9006431bee2 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -284,6 +284,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/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 0000000000000..846663621e868
--- /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 :c:member:`!tp_dict` is now always ``NULL``.  :c:func:`!PyType_GetDict()`
+provides the correct dict object instead.
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 50eaff9917fd1..dd2c9c72e5378 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -638,6 +638,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))
 {
@@ -3472,6 +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_thread_state",      test_thread_state,               METH_VARARGS},
 #ifndef MS_WINDOWS
     {"_spawn_pthread_waiter",   spawn_pthread_waiter,            METH_NOARGS},
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 87519efef081c..5430924e69dee 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -237,6 +237,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)
 {



More information about the Python-checkins mailing list