Skip to content

Commit

Permalink
GH-92678: Document that you shouldn't be doing your own dictionary of…
Browse files Browse the repository at this point in the history
…fset calculations. (GH-95598) (GH-95821)

Co-authored-by: Petr Viktorin <[email protected]>
Co-authored-by: Stanley <[email protected]>
Co-authored-by: Mark Shannon <[email protected]>
  • Loading branch information
4 people authored Aug 9, 2022
1 parent 2d36d5e commit c96b26c
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 13 deletions.
18 changes: 18 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ Object Protocol
A generic implementation for the getter of a ``__dict__`` descriptor. It
creates the dictionary if necessary.
This function may also be called to get the :py:attr:`~object.__dict__`
of the object *o*. Pass ``NULL`` for *context* when calling it.
Since this function may need to allocate memory for the
dictionary, it may be more efficient to call :c:func:`PyObject_GetAttr`
when accessing an attribute on the object.
On failure, returns ``NULL`` with an exception set.
.. versionadded:: 3.3
Expand All @@ -137,6 +145,16 @@ Object Protocol
.. versionadded:: 3.3
.. c:function:: PyObject** _PyObject_GetDictPtr(PyObject *obj)
Return a pointer to :py:attr:`~object.__dict__` of the object *obj*.
If there is no ``__dict__``, return ``NULL`` without setting an exception.
This function may need to allocate memory for the
dictionary, so it may be more efficient to call :c:func:`PyObject_GetAttr`
when accessing an attribute on the object.
.. c:function:: PyObject* PyObject_RichCompare(PyObject *o1, PyObject *o2, int opid)
Compare the values of *o1* and *o2* using the operation specified by *opid*,
Expand Down
17 changes: 5 additions & 12 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1709,18 +1709,11 @@ and :c:type:`PyType_Type` effectively act as defaults.)
:c:member:`~PyTypeObject.tp_dictoffset` should be set to ``-4`` to indicate that the dictionary is
at the very end of the structure.

The real dictionary offset in an instance can be computed from a negative
:c:member:`~PyTypeObject.tp_dictoffset` as follows::

dictoffset = tp_basicsize + abs(ob_size)*tp_itemsize + tp_dictoffset
if dictoffset is not aligned on sizeof(void*):
round up to sizeof(void*)

where :c:member:`~PyTypeObject.tp_basicsize`, :c:member:`~PyTypeObject.tp_itemsize` and :c:member:`~PyTypeObject.tp_dictoffset` are
taken from the type object, and :attr:`ob_size` is taken from the instance. The
absolute value is taken because ints use the sign of :attr:`ob_size` to
store the sign of the number. (There's never a need to do this calculation
yourself; it is done for you by :c:func:`_PyObject_GetDictPtr`.)
The :c:member:`~PyTypeObject.tp_dictoffset` should be regarded as write-only.
To get the pointer to the dictionary call :c:func:`PyObject_GenericGetDict`.
Calling :c:func:`PyObject_GenericGetDict` may need to allocate memory for the
dictionary, so it is may be more efficient to call :c:func:`PyObject_GetAttr`
when accessing an attribute on the object.

**Inheritance:**

Expand Down
4 changes: 4 additions & 0 deletions Doc/whatsnew/3.11.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,10 @@ Changes in the Python API
:func:`compile` and other related functions. If invalid positions are detected,
a :exc:`ValueError` will be raised. (Contributed by Pablo Galindo in :gh:`93351`)

* :c:member:`~PyTypeObject.tp_dictoffset` should be treated as write-only.
It can be set to describe C extension clases to the VM, but should be regarded
as meaningless when read. To get the pointer to the object's dictionary call
:c:func:`PyObject_GenericGetDict` instead.

Build Changes
=============
Expand Down
6 changes: 5 additions & 1 deletion Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,11 @@ _PyObject_DictPointer(PyObject *obj)

/* Helper to get a pointer to an object's __dict__ slot, if any.
* Creates the dict from inline attributes if necessary.
* Does not set an exception. */
* Does not set an exception.
*
* Note that the tp_dictoffset docs used to recommend this function,
* so it should be treated as part of the public API.
*/
PyObject **
_PyObject_GetDictPtr(PyObject *obj)
{
Expand Down

0 comments on commit c96b26c

Please sign in to comment.