From 8604993e4e1f7962ac7a579d68e9fb167303e2b8 Mon Sep 17 00:00:00 2001 From: Robin Narsingh Ranabhat Date: Wed, 13 Aug 2025 16:20:14 -0500 Subject: [PATCH 1/4] gh-137740: Clarify __del__ invocation mechanism in reference counting example --- Doc/extending/extending.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 17c6fb224265ca..acf4d0b912586c 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1084,7 +1084,12 @@ references to all its items, so when item 1 is replaced, it has to dispose of the original item 1. Now let's suppose the original item 1 was an instance of a user-defined class, and let's further suppose that the class defined a :meth:`!__del__` method. If this class instance has a reference count of 1, -disposing of it will call its :meth:`!__del__` method. +disposing of it will call its :meth:`!__del__` method. Internally, +:c:func:`PyList_SetItem` calls :c:func:`Py_XDECREF` on the replaced item, +which invokes the object's ``tp_dealloc`` function (``subtype_dealloc`` for Python +class instances). During deallocation, ``subtype_dealloc`` calls ``tp_finalize``, +which is mapped to the ``__del__`` method for Python classes (see :pep:`442`). +This entire sequence happens synchronously within the :c:func:`PyList_SetItem` call. Since it is written in Python, the :meth:`!__del__` method can execute arbitrary Python code. Could it perhaps do something to invalidate the reference to From 567682cc888787a5e4dcc0432bb2aeb0c7a9b2c0 Mon Sep 17 00:00:00 2001 From: Robin Narsingh Ranabhat Date: Wed, 13 Aug 2025 22:28:12 -0500 Subject: [PATCH 2/4] updated Sphinx references --- Doc/extending/extending.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index acf4d0b912586c..f2f4017d57bfaf 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1085,10 +1085,10 @@ the original item 1. Now let's suppose the original item 1 was an instance of a user-defined class, and let's further suppose that the class defined a :meth:`!__del__` method. If this class instance has a reference count of 1, disposing of it will call its :meth:`!__del__` method. Internally, -:c:func:`PyList_SetItem` calls :c:func:`Py_XDECREF` on the replaced item, -which invokes the object's ``tp_dealloc`` function (``subtype_dealloc`` for Python -class instances). During deallocation, ``subtype_dealloc`` calls ``tp_finalize``, -which is mapped to the ``__del__`` method for Python classes (see :pep:`442`). +:c:func:`PyList_SetItem` calls :c:func:`Py_DECREF` on the replaced item, +which invokes replaced item's corrresponding :c:member:`~PyTypeObject.tp_dealloc` function (i.e :c:func:`subtype_dealloc` in case of Python +class instance). During deallocation, :c:func:`subtype_dealloc` calls :c:member:`~PyTypeObject.tp_finalize`, +which is mapped to the :meth:`!__del__` method for class instances (see :pep:`442`). This entire sequence happens synchronously within the :c:func:`PyList_SetItem` call. Since it is written in Python, the :meth:`!__del__` method can execute arbitrary From c4f5f6bc0c44105398dbb37ce91f3ed4df3b733b Mon Sep 17 00:00:00 2001 From: Robin Narsingh Ranabhat Date: Thu, 14 Aug 2025 09:24:31 -0500 Subject: [PATCH 3/4] limit line length, easier abbreviation --- Doc/extending/extending.rst | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index f2f4017d57bfaf..974ef9bc82ef81 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1086,10 +1086,13 @@ user-defined class, and let's further suppose that the class defined a :meth:`!__del__` method. If this class instance has a reference count of 1, disposing of it will call its :meth:`!__del__` method. Internally, :c:func:`PyList_SetItem` calls :c:func:`Py_DECREF` on the replaced item, -which invokes replaced item's corrresponding :c:member:`~PyTypeObject.tp_dealloc` function (i.e :c:func:`subtype_dealloc` in case of Python -class instance). During deallocation, :c:func:`subtype_dealloc` calls :c:member:`~PyTypeObject.tp_finalize`, -which is mapped to the :meth:`!__del__` method for class instances (see :pep:`442`). -This entire sequence happens synchronously within the :c:func:`PyList_SetItem` call. +which invokes replaced item's corrresponding +:c:member:`~PyTypeObject.tp_dealloc` function (that is +:c:func:`subtype_dealloc` in case of Python class instance). During +deallocation, :c:func:`subtype_dealloc` calls +:c:member:`~PyTypeObject.tp_finalize`, which is mapped to the +:meth:`!__del__` method for class instances (see :pep:`442`). This entire +sequence happens synchronously within the :c:func:`PyList_SetItem` call. Since it is written in Python, the :meth:`!__del__` method can execute arbitrary Python code. Could it perhaps do something to invalidate the reference to From e29e709820959ef2e71115b8475f777c87aae227 Mon Sep 17 00:00:00 2001 From: Robin Narsingh Ranabhat Date: Thu, 14 Aug 2025 22:24:49 +0545 Subject: [PATCH 4/4] Update Doc/extending/extending.rst Co-authored-by: AN Long --- Doc/extending/extending.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/extending/extending.rst b/Doc/extending/extending.rst index 974ef9bc82ef81..c55c5fc2a4a197 100644 --- a/Doc/extending/extending.rst +++ b/Doc/extending/extending.rst @@ -1086,7 +1086,7 @@ user-defined class, and let's further suppose that the class defined a :meth:`!__del__` method. If this class instance has a reference count of 1, disposing of it will call its :meth:`!__del__` method. Internally, :c:func:`PyList_SetItem` calls :c:func:`Py_DECREF` on the replaced item, -which invokes replaced item's corrresponding +which invokes replaced item's corresponding :c:member:`~PyTypeObject.tp_dealloc` function (that is :c:func:`subtype_dealloc` in case of Python class instance). During deallocation, :c:func:`subtype_dealloc` calls