Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

gh-75459: Doc: C API: Improve object life cycle documentation #125962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bc32398
gh-75459: Doc: C API: Improve object life cycle documentation
rhansen Oct 19, 2024
361eaca
gh-75459: Doc: Tell sphinx to run graphviz
rhansen Oct 25, 2024
b27bcca
add blurb
rhansen Oct 25, 2024
b42b58d
Revert "gh-75459: Doc: Tell sphinx to run graphviz"
rhansen Oct 25, 2024
7e571ae
delete "ref count == 0" node, link directly to `tp_dealloc`
rhansen Oct 26, 2024
97dc30c
Merge branch 'main' into docs
rhansen Nov 7, 2024
85e535a
significant rewrite to address review comments
rhansen Nov 7, 2024
ef979e6
fix warnings
rhansen Nov 7, 2024
f3863c4
tweak cyclic isolate definition
rhansen Nov 7, 2024
6a114f9
apply css to the svg to support dark theme
rhansen Nov 8, 2024
182c977
increase font size
rhansen Nov 8, 2024
7cd0cb5
attempt to make the svg accessible
rhansen Nov 8, 2024
e34e224
wrap at 79 chars
rhansen Nov 8, 2024
16a29ab
address review feedback, and other tweaks
rhansen Nov 9, 2024
018a3c4
be more precise about the finalized mark
rhansen Nov 21, 2024
442b7f2
add pdf support, improve epub support
rhansen Nov 21, 2024
5ed484a
address feedback, and other improvements
rhansen Nov 27, 2024
b7774ae
Merge in the main branch
encukou Mar 11, 2025
bb1a94f
Remove redundant notes added in GH-129850
encukou Mar 11, 2025
fd38fd4
Merge branch 'main' into docs
ZeroIntensity May 5, 2025
3573c79
Apply suggestions from code review
ZeroIntensity May 5, 2025
2633594
Update Doc/c-api/lifecycle.rst
ZeroIntensity May 5, 2025
c57574a
Mention tp_alloc()
ZeroIntensity May 5, 2025
878fd27
Avoid using 'do not'
ZeroIntensity May 5, 2025
1b96fad
Mention PyObject_CallFinalizerFromDealloc()
ZeroIntensity May 5, 2025
e8c6852
Add some clarifications about tp_finalize()
ZeroIntensity May 5, 2025
3408919
Say that tp_dealloc() must free.
ZeroIntensity May 5, 2025
b6023a3
Fix doctest.
ZeroIntensity May 5, 2025
da3593c
Apply suggestions from code review
ZeroIntensity May 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions 6 .github/workflows/reusable-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: 'Install Dependencies'
run: |
sudo apt-get update &&
sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
graphviz \
;
- uses: actions/checkout@v4
- name: 'Set up Python'
uses: actions/setup-python@v5
Expand Down
43 changes: 22 additions & 21 deletions 43 .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,26 @@ build:
os: ubuntu-24.04
tools:
python: "3"
apt_packages:
- graphviz
rhansen marked this conversation as resolved.
Show resolved Hide resolved
jobs:
post_checkout:
rhansen marked this conversation as resolved.
Show resolved Hide resolved
# https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition
#
# Cancel building pull requests when there aren't changes in the Doc directory.
#
# If there are no changes (git diff exits with 0) we force the command to return with 183.
# This is a special exit code on Read the Docs that will cancel the build immediately.
- |
if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ];
then
echo "No changes to Doc/ - exiting the build.";
exit 183;
fi

commands:
# https://docs.readthedocs.io/en/stable/build-customization.html#cancel-build-based-on-a-condition
#
# Cancel building pull requests when there aren't changes in the Doc directory.
#
# If there are no changes (git diff exits with 0) we force the command to return with 183.
# This is a special exit code on Read the Docs that will cancel the build immediately.
- |
if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && [ "$(git diff --quiet origin/main -- Doc/ .readthedocs.yml; echo $?)" -eq 0 ];
then
echo "No changes to Doc/ - exiting the build.";
exit 183;
fi

- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
- make -C Doc venv html
- mkdir _readthedocs
- mv Doc/build/html _readthedocs/html

- asdf plugin add uv
- asdf install uv latest
- asdf global uv latest
- make -C Doc venv html
- mkdir _readthedocs
- mv Doc/build/html _readthedocs/html
54 changes: 36 additions & 18 deletions 54 Doc/c-api/allocation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Allocating Objects on the Heap
reference. Returns the initialized object. If *type* indicates that the
object participates in the cyclic garbage detector, it is added to the
detector's set of observed objects. Other fields of the object are not
affected.
initialized. Specifically, this function does **not** call the object's
:meth:`~object.__init__` method (:c:member:`~PyTypeObject.tp_init` slot).


.. c:function:: PyVarObject* PyObject_InitVar(PyVarObject *op, PyTypeObject *type, Py_ssize_t size)
Expand All @@ -29,27 +30,44 @@ Allocating Objects on the Heap

.. c:macro:: PyObject_New(TYPE, typeobj)

Allocate a new Python object using the C structure type *TYPE*
and the Python type object *typeobj* (``PyTypeObject*``).
Fields not defined by the Python object header are not initialized.
The caller will own the only reference to the object
(i.e. its reference count will be one).
The size of the memory allocation is determined from the
:c:member:`~PyTypeObject.tp_basicsize` field of the type object.
Calls :c:func:`PyObject_Malloc` to allocate memory for a new Python object
using the C structure type *TYPE* and the Python type object *typeobj*
(``PyTypeObject*``), then initializes the memory like
:c:func:`PyObject_Init`. The caller will own the only reference to the
object (i.e. its reference count will be one). The size of the memory
allocation is determined from the :c:member:`~PyTypeObject.tp_basicsize`
field of the type object.
rhansen marked this conversation as resolved.
Show resolved Hide resolved
rhansen marked this conversation as resolved.
Show resolved Hide resolved

This does not call :c:member:`~PyTypeObject.tp_alloc`,
:c:member:`~PyTypeObject.tp_new` (:meth:`~object.__new__`), or
:c:member:`~PyTypeObject.tp_init` (:meth:`~object.__init__`).

This should not be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set
in :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_New`
instead.

Memory allocated by this function must be freed with :c:func:`PyObject_Free`.


.. c:macro:: PyObject_NewVar(TYPE, typeobj, size)

Allocate a new Python object using the C structure type *TYPE* and the
Python type object *typeobj* (``PyTypeObject*``).
Fields not defined by the Python object header
are not initialized. The allocated memory allows for the *TYPE* structure
plus *size* (``Py_ssize_t``) fields of the size
given by the :c:member:`~PyTypeObject.tp_itemsize` field of
*typeobj*. This is useful for implementing objects like tuples, which are
able to determine their size at construction time. Embedding the array of
fields into the same allocation decreases the number of allocations,
improving the memory management efficiency.
Like :c:macro:`PyObject_New` except:

* It allocates enough memory for the *TYPE* structure plus *size*
(``Py_ssize_t``) fields of the size given by the
:c:member:`~PyTypeObject.tp_itemsize` field of *typeobj*.
* The memory is initialized like :c:func:`PyObject_InitVar`.

This is useful for implementing objects like tuples, which are able to
determine their size at construction time. Embedding the array of fields
into the same allocation decreases the number of allocations, improving the
memory management efficiency.

This should not be used for objects with :c:macro:`Py_TPFLAGS_HAVE_GC` set
in :c:member:`~PyTypeObject.tp_flags`; use :c:macro:`PyObject_GC_NewVar`
instead.

Memory allocated by this function must be freed with :c:func:`PyObject_Free`.


.. c:function:: void PyObject_Del(void *op)
Expand Down
9 changes: 9 additions & 0 deletions 9 Doc/c-api/gcsupport.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,17 @@ rules:
Analogous to :c:macro:`PyObject_New` but for container objects with the
:c:macro:`Py_TPFLAGS_HAVE_GC` flag set.

Memory allocated by this function must be freed with
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
:c:func:`PyObject_GC_Del`.
rhansen marked this conversation as resolved.
Show resolved Hide resolved

.. c:macro:: PyObject_GC_NewVar(TYPE, typeobj, size)

Analogous to :c:macro:`PyObject_NewVar` but for container objects with the
:c:macro:`Py_TPFLAGS_HAVE_GC` flag set.

Memory allocated by this function must be freed with
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
:c:func:`PyObject_GC_Del`.

.. c:function:: PyObject* PyUnstable_Object_GC_NewWithExtraData(PyTypeObject *type, size_t extra_size)

Analogous to :c:macro:`PyObject_GC_New` but allocates *extra_size*
Expand All @@ -73,6 +79,9 @@ rules:
The extra data will be deallocated with the object, but otherwise it is
not managed by Python.

Memory allocated by this function must be freed with
:c:func:`PyObject_GC_Del`.

.. warning::
The function is marked as unstable because the final mechanism
for reserving extra data after an instance is not yet decided.
Expand Down
173 changes: 173 additions & 0 deletions 173 Doc/c-api/lifecycle.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
.. highlight:: c

.. _life-cycle:

Object Life Cycle
=================

Stages
------

The following is an illustration of the stages of life of an object. Arrows
indicate a "happens before" relationship. Octagons indicate functions specific
to :ref:`garbage collection support <supporting-cycle-detection>`.
rhansen marked this conversation as resolved.
Show resolved Hide resolved

.. digraph:: callorder

graph [
fontname="svg"
fontsize=10.0
layout="dot"
ranksep=0.25
]
node [
fontname="Courier"
fontsize=10.0
]
edge [
fontname="Times-Italic"
fontsize=10.0
]

"start" [fontname="Times-Italic" shape=plain label=< start > style=invis]
"tp_alloc" [href="typeobj.html#c.PyTypeObject.tp_alloc" target="_top"]
"tp_new" [href="typeobj.html#c.PyTypeObject.tp_new" target="_top"]
"tp_init" [href="typeobj.html#c.PyTypeObject.tp_init" target="_top"]
{
rank="same"
"alive" [
fontname="Times-Italic"
label=<alive, ref count &gt; 0>
shape=box
]
"tp_traverse" [
href="typeobj.html#c.PyTypeObject.tp_traverse"
shape=octagon
target="_top"
]
}
"tp_finalize" [
href="typeobj.html#c.PyTypeObject.tp_finalize"
shape=octagon
target="_top"
]
"tp_clear" [
href="typeobj.html#c.PyTypeObject.tp_clear"
shape=octagon
target="_top"
]
"ref0" [
fontname="Times-Italic"
label=<ref count == 0>
ordering="in"
shape=box
]
"tp_dealloc" [href="typeobj.html#c.PyTypeObject.tp_dealloc" target="_top"]
"tp_free" [href="typeobj.html#c.PyTypeObject.tp_free" target="_top"]

"start" -> "tp_alloc"
"tp_alloc" -> "tp_new"
"tp_new" -> "tp_init"
"tp_init" -> "alive"
"tp_traverse" -> "alive"
"alive" -> "tp_traverse"
"alive" -> "tp_clear" [label=< cyclic <br/>isolate >]
"alive" -> "tp_finalize" [
dir="back"
label=< resurrected >
]
"alive" -> "tp_finalize" [label=< cyclic <br/>isolate >]
"tp_finalize" -> "tp_clear"
"tp_finalize" -> "ref0"
"tp_clear" -> "ref0"
"tp_clear" -> "tp_dealloc" [
dir="back"
label=< optional<br/>direct call >
]
"alive" -> "ref0"
"ref0" -> "tp_dealloc"
"tp_finalize" -> "tp_dealloc" [
dir="back"
href="lifecycle.html#c.PyObject_CallFinalizerFromDealloc"
label=<
<table border="0" cellborder="0" cellpadding="0" cellspacing="0">
<tr>
<td rowspan="4"> </td>
<td align="left">optional call to</td>
<td rowspan="4"> </td>
</tr>
<tr>
<td align="left"><font face="Courier">PyObject_Call</font></td>
</tr>
<tr>
<td align="left"><font face="Courier">FinalizerFrom</font></td>
</tr>
<tr><td align="left"><font face="Courier">Dealloc</font></td></tr>
</table>
>
target="_top"
]
"tp_dealloc" -> "tp_free" [label=< directly calls >]

Explanation:

* :c:member:`~PyTypeObject.tp_alloc`, :c:member:`~PyTypeObject.tp_new`, and
:c:member:`~PyTypeObject.tp_init` are called to allocate memory for a new
object and initialize the object.
* If the reference count for an object drops to 0,
:c:member:`~PyTypeObject.tp_dealloc` is called to destroy the object.
* :c:member:`~PyTypeObject.tp_dealloc` can optionally call
:c:member:`~PyTypeObject.tp_finalize` (if non-``NULL``) via
:c:func:`PyObject_CallFinalizerFromDealloc` if it wishes to reuse that code
to help with object destruction.
* :c:member:`~PyTypeObject.tp_finalize` may increase the object's reference
count, halting the destruction. The object is said to be resurrected.
* :c:member:`~PyTypeObject.tp_dealloc` can optionally call
:c:member:`~PyTypeObject.tp_clear` (if non-``NULL``) if it wishes to reuse
that code to help with object destruction.
* When :c:member:`~PyTypeObject.tp_dealloc` finishes object destruction, it
directly calls :c:member:`~PyTypeObject.tp_free` to deallocate the memory.

If the object is marked as supporting garbage collection (the
:c:macro:`Py_TPFLAGS_HAVE_GC` flag is set in
:c:member:`~PyTypeObject.tp_flags`), the following stages are also possible:

* The garbage collector occasionally calls
:c:member:`~PyTypeObject.tp_traverse` to identify :term:`cyclic isolates
<cyclic isolate>`.
* When the garbage collector discovers a cyclic isolate, it finalizes one of
the objects in the group by calling its :c:member:`~PyTypeObject.tp_finalize`
function. This repeats until the cyclic isolate doesn't exist or all of the
objects have been finalized.
* The :c:member:`~PyTypeObject.tp_finalize` function can optionally increase
the object's reference count, causing it (and other objects it references) to
become resurrected and no longer a member of a cyclic isolate.
* When the garbage collector discovers a cyclic isolate and all of the objects
in the group have already been finalized, the garbage collector clears one of
the objects in the group by calling its :c:member:`~PyTypeObject.tp_clear`
function. This repeats until the cyclic isolate doesn't exist or all of the
objects have been cleared.


Functions
---------

To allocate and free memory, see :ref:`Allocating Objects on the Heap
<allocating-objects>`.
rhansen marked this conversation as resolved.
Show resolved Hide resolved


.. c:function:: void PyObject_CallFinalizer(PyObject *op)

Calls the object's finalizer (:c:member:`~PyTypeObject.tp_finalize`) if it
has not already been called.


.. c:function:: int PyObject_CallFinalizerFromDealloc(PyObject *op)

Calls the object's finalizer (:c:member:`~PyTypeObject.tp_finalize`) if it
has not already been called. This function is intended to be called at the
beginning of the object's destructor (:c:member:`~PyTypeObject.tp_dealloc`).
The object's reference count must already be 0. If the object's finalizer
increases the object's reference count, the object is resurrected and this
function returns -1; no further destruction should happen. Otherwise, this
function returns 0 and destruction can continue normally.
1 change: 1 addition & 0 deletions 1 Doc/c-api/objimpl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ object types.
.. toctree::

allocation.rst
lifecycle.rst
structures.rst
typeobj.rst
gcsupport.rst
Loading
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.