Skip to content

Commit ad723c6

Browse files
committed
gh-139852: Add PyObject_GetDictPtr() function
1 parent d2deb8f commit ad723c6

File tree

7 files changed

+93
-17
lines changed

7 files changed

+93
-17
lines changed

Doc/c-api/object.rst

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -287,14 +287,26 @@ Object Protocol
287287
.. versionadded:: 3.3
288288
289289
290-
.. c:function:: PyObject** _PyObject_GetDictPtr(PyObject *obj)
290+
.. c:function:: int PyObject_GetDictPtr(PyObject *obj, PyObject ***dict_ptr)
291291
292-
Return a pointer to :py:attr:`~object.__dict__` of the object *obj*.
293-
If there is no ``__dict__``, return ``NULL`` without setting an exception.
292+
Get a pointer to :py:attr:`~object.__dict__` of the object *obj*.
294293
295-
This function may need to allocate memory for the
296-
dictionary, so it may be more efficient to call :c:func:`PyObject_GetAttr`
297-
when accessing an attribute on the object.
294+
* If there is a ``__dict__``, set *\*dict_ptr* and return ``1``.
295+
* If there is no ``__dict__``, set *\*dict_ptr* to ``NULL`` without setting
296+
an exception and return ``0``.
297+
* On error, set an exception and return ``-1``.
298+
299+
This function may need to allocate memory for the dictionary, so it may be
300+
more efficient to call :c:func:`PyObject_GetAttr` when accessing an
301+
attribute on the object.
302+
303+
.. versionadded:: next
304+
305+
306+
.. c:function:: PyObject** _PyObject_GetDictPtr(PyObject *obj)
307+
308+
Similar to :c:func:`PyObject_GetDictPtr`, but ignore errors silently (return
309+
``NULL`` without setting an exception).
298310
299311
300312
.. c:function:: PyObject* PyObject_RichCompare(PyObject *o1, PyObject *o2, int opid)

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,10 @@ New features
852852

853853
(Contributed by Victor Stinner in :gh:`129813`.)
854854

855+
* Add :c:type:`PyObject_GetDictPtr` function to get a pointer to
856+
:py:attr:`~object.__dict__` of an object.
857+
(Contributed by Victor Stinner in :gh:`139852`.)
858+
855859

856860
Porting to Python 3.15
857861
----------------------

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
299299

300300
PyAPI_FUNC(PyObject*) _PyObject_GetAttrId(PyObject *, _Py_Identifier *);
301301

302+
PyAPI_FUNC(int) PyObject_GetDictPtr(PyObject *obj, PyObject ***dict_ptr);
302303
PyAPI_FUNC(PyObject **) _PyObject_GetDictPtr(PyObject *);
303304
PyAPI_FUNC(void) PyObject_CallFinalizer(PyObject *);
304305
PyAPI_FUNC(int) PyObject_CallFinalizerFromDealloc(PyObject *);

Lib/test/test_capi/test_object.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,5 +247,24 @@ def func(x):
247247

248248
func(object())
249249

250+
def test_object_getdictptr(self):
251+
object_getdictptr = _testcapi.object_getdictptr
252+
253+
class MyClass:
254+
pass
255+
obj = MyClass()
256+
obj.attr = 123
257+
258+
dict1 = object_getdictptr(obj)
259+
dict2 = obj.__dict__
260+
self.assertIs(dict1, dict2)
261+
262+
class NoDict:
263+
__slots__ = ()
264+
obj = NoDict()
265+
266+
self.assertEqual(object_getdictptr(obj), AttributeError)
267+
268+
250269
if __name__ == "__main__":
251270
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :c:type:`PyObject_GetDictPtr` function to get a pointer to
2+
:py:attr:`~object.__dict__` of an object. Patch by Victor Stinner.

Modules/_testcapi/object.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,28 @@ is_uniquely_referenced(PyObject *self, PyObject *op)
485485
}
486486

487487

488+
static PyObject *
489+
object_getdictptr(PyObject *self, PyObject *obj)
490+
{
491+
NULLABLE(obj);
492+
493+
PyObject **dict_ptr = UNINITIALIZED_PTR;
494+
switch (PyObject_GetDictPtr(obj, &dict_ptr)) {
495+
case -1:
496+
assert(dict_ptr == NULL);
497+
return NULL;
498+
case 0:
499+
assert(dict_ptr == NULL);
500+
return Py_NewRef(PyExc_AttributeError);
501+
case 1:
502+
return Py_NewRef(*dict_ptr);
503+
default:
504+
Py_FatalError("PyObject_GetDictPtr() returned invalid code");
505+
Py_UNREACHABLE();
506+
}
507+
}
508+
509+
488510
static PyMethodDef test_methods[] = {
489511
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
490512
{"pyobject_print_null", pyobject_print_null, METH_VARARGS},
@@ -511,6 +533,7 @@ static PyMethodDef test_methods[] = {
511533
{"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
512534
{"clear_managed_dict", clear_managed_dict, METH_O, NULL},
513535
{"is_uniquely_referenced", is_uniquely_referenced, METH_O},
536+
{"object_getdictptr", object_getdictptr, METH_O},
514537
{NULL},
515538
};
516539

Objects/object.c

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,6 +1540,27 @@ _PyObject_ComputedDictPointer(PyObject *obj)
15401540
return (PyObject **) ((char *)obj + dictoffset);
15411541
}
15421542

1543+
int
1544+
PyObject_GetDictPtr(PyObject *obj, PyObject ***dict_ptr)
1545+
{
1546+
if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
1547+
*dict_ptr = _PyObject_ComputedDictPointer(obj);
1548+
return (*dict_ptr != NULL);
1549+
}
1550+
1551+
PyDictObject *dict = _PyObject_GetManagedDict(obj);
1552+
if (dict == NULL && Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
1553+
dict = _PyObject_MaterializeManagedDict(obj);
1554+
if (dict == NULL) {
1555+
*dict_ptr = NULL;
1556+
return -1;
1557+
}
1558+
}
1559+
*dict_ptr = (PyObject **)&_PyObject_ManagedDictPointer(obj)->dict;
1560+
assert(*dict_ptr != NULL);
1561+
return 1;
1562+
}
1563+
15431564
/* Helper to get a pointer to an object's __dict__ slot, if any.
15441565
* Creates the dict from inline attributes if necessary.
15451566
* Does not set an exception.
@@ -1550,18 +1571,12 @@ _PyObject_ComputedDictPointer(PyObject *obj)
15501571
PyObject **
15511572
_PyObject_GetDictPtr(PyObject *obj)
15521573
{
1553-
if ((Py_TYPE(obj)->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
1554-
return _PyObject_ComputedDictPointer(obj);
1555-
}
1556-
PyDictObject *dict = _PyObject_GetManagedDict(obj);
1557-
if (dict == NULL && Py_TYPE(obj)->tp_flags & Py_TPFLAGS_INLINE_VALUES) {
1558-
dict = _PyObject_MaterializeManagedDict(obj);
1559-
if (dict == NULL) {
1560-
PyErr_Clear();
1561-
return NULL;
1562-
}
1574+
PyObject **dict_ptr;
1575+
if (PyObject_GetDictPtr(obj, &dict_ptr) < 0) {
1576+
PyErr_Clear();
1577+
dict_ptr = NULL;
15631578
}
1564-
return (PyObject **)&_PyObject_ManagedDictPointer(obj)->dict;
1579+
return dict_ptr;
15651580
}
15661581

15671582
PyObject *

0 commit comments

Comments
 (0)