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

Explain how to add an extension module #1350

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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Sep 11, 2024
Merged
Changes from 1 commit
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
086ded3
explain how to add modules
picnixz Jul 13, 2024
96fb998
Update extension-modules.rst
picnixz Jul 13, 2024
5f8797c
Update extension-modules.rst
picnixz Jul 13, 2024
e5d41f8
Update extension-modules.rst
picnixz Jul 13, 2024
b740114
Update extension-modules.rst
picnixz Jul 13, 2024
c74df67
Update extension-modules.rst
picnixz Jul 13, 2024
e645869
Update extension-modules.rst
picnixz Jul 14, 2024
5a9a57c
Update extension-modules.rst
picnixz Jul 14, 2024
26a18eb
Update extension-modules.rst
picnixz Jul 14, 2024
1b4d73f
Update extension-modules.rst
picnixz Jul 14, 2024
94086dd
Address Hugo's feedback
picnixz Jul 14, 2024
9a78a3b
Update extension-modules.rst
picnixz Jul 14, 2024
e22c278
Update extension-modules.rst
picnixz Jul 14, 2024
1f51497
improvements
picnixz Jul 15, 2024
bdf09e5
fixup! sphinx
picnixz Jul 15, 2024
ef7cf3e
fixup! indents
picnixz Jul 15, 2024
1a405ba
fixup! warnings
picnixz Jul 15, 2024
e39cbc2
improve sections
picnixz Jul 15, 2024
35f207f
fix markup
picnixz Jul 15, 2024
316b00d
improve titles
picnixz Jul 15, 2024
523dece
improve presentation
picnixz Jul 15, 2024
d1cdd1d
fixup! markup
picnixz Jul 15, 2024
defb31e
simplify snippets
picnixz Jul 15, 2024
6213438
improvements
picnixz Jul 15, 2024
f6e5d55
improvements
picnixz Jul 15, 2024
d1a1ed5
some rewordings and cleanups
picnixz Jul 15, 2024
86e3e54
simplify wording
picnixz Jul 16, 2024
65f62e7
address Erlend's review
picnixz Jul 17, 2024
4b7c7d8
fix indents?
picnixz Jul 17, 2024
7abb6f1
add ref to clinic everywhere when needed
picnixz Jul 17, 2024
da9b58b
fix typos
picnixz Jul 17, 2024
783e6db
address encukou's review
picnixz Jul 18, 2024
8906ebd
improve the page flow
picnixz Jul 18, 2024
128c81c
use sentence case
picnixz Jul 18, 2024
7d6c8d6
add podman tip
picnixz Jul 18, 2024
01c25bc
address rest of the review
picnixz Jul 18, 2024
56910fb
address Alyssa's review
picnixz Jul 18, 2024
3ee1cea
add details
picnixz Jul 18, 2024
3d235f4
address review
picnixz Aug 4, 2024
7b0b234
Make it easier to update the required ubuntu version
picnixz Aug 4, 2024
7fa94bf
Merge remote-tracking branch 'upstream/main' into add-extensions-tuto…
picnixz Aug 4, 2024
ec48fdb
fixup!
picnixz Aug 4, 2024
1843e3d
fixup!
picnixz Aug 4, 2024
18c4d91
improve comment
picnixz Aug 14, 2024
8224bbd
use double quotes instead of single quotes
picnixz Aug 14, 2024
2f63053
Address Carol's review.
picnixz Sep 11, 2024
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
Next Next commit
explain how to add modules
  • Loading branch information
picnixz committed Jul 13, 2024
commit 086ded34264c674e5f20be5a276d03bd574fdc32
314 changes: 312 additions & 2 deletions 314 developer-workflow/extension-modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,323 @@
Standard library extension modules
==================================
picnixz marked this conversation as resolved.
Show resolved Hide resolved

In this section, we could explain how to write a CPython extension with the C language, but the topic can take a complete book.
In this section, we could explain how to write a CPython extension with the
C language, but the topic can take a complete book. We will however explain
how to add a new extension module to the standard library, e.g., a module
picnixz marked this conversation as resolved.
Show resolved Hide resolved
responsible for accelerating some parts of the library.

For this reason, we prefer to give you some links where you can read a very good documentation.
For writing a CPython extension itself, we prefer to give you some links
where you can read a very good documentation.

Read the following references:
picnixz marked this conversation as resolved.
Show resolved Hide resolved

* https://docs.python.org/dev/c-api/
* https://docs.python.org/dev/extending/
* :pep:`399`
* https://pythonextensionpatterns.readthedocs.io/en/latest/

=====================================
Adding an extension module to CPython
=====================================

In this section, we assume that the extension module to be added
does not rely on external dependencies and is not a frozen module.

Let us assume that the standard library has the :mod:`!foo` module
which contains some function :func:`!foo.bar`:

.. code-block:: python

def bar():
return "Hello World!"

Instead of using the Python implementation of :func:`!foo.bar`, we want to
use its C implementation that we would have written somewhere else. Ideally,
we want to modify ``foo.py`` as follows:

.. code-block:: python

try:
# use the C implementation if possible
from _foo import bar
except ImportError:
# fallback to the pure Python implementation
def bar():
return "Hello World!"

Some modules in the standard library are implemented both in C and in Python,
such as :mod:`functools` or :mod:`io`, and the C implementation should offer
improved performances when available (such modules are usually referred to as
*accelerator modules*). In our example, we need to

- determine where the extension module is to be placed;
- determine which files to modify in order to compile the project;
- determine which Makefile rules to invoke in the end.
picnixz marked this conversation as resolved.
Show resolved Hide resolved

In general, accelerator modules are added in the ``Modules/`` directory
of the CPython project. If more than one file is needed for the extension
module, it is convenient to create a sub-directory in ``Modules/`` and place
the files inside it. For instance,

.. code-block:: c

// Modules/foo/foomodule.h: file containing shared prototypes

#ifndef FOOMODULE_H
#define FOOMODULE_H

#include "Python.h"

typedef struct {
/* ... */
} foomodule_state;

static inline foomodule_state *
get_foomodule_state(PyObject *module)
{
void *state = PyModule_GetState(module);
assert(state != NULL);
return (foomodule_state *)state;
}

/* helper implemented somewhere else */
extern PyObject *_Py_fast_bar();

#endif // FOOMODULE_H

The actual implementation of the module is in the corresponding ``.c`` file:

.. code-block:: c

// Modules/foo/foomodule.c

#include "foomodule.h"
#include "clinic/foomodule.c.h"

/* Functions for the module's state */
static int
foomodule_exec(PyObject *module)
{
// imports, static attributes, exported classes, etc
return 0;
}

static int
foomodule_traverse(PyObject *m, visitproc visit, void *arg)
{
foomodule_state *st = get_foomodule_state(m);
// call Py_VISIT() on the state attributes
return 0;
}

static int
foomodule_clear(PyObject *m)
{
foomodule_state *st = get_foomodule_state(m);
// call Py_CLEAR() on the state attributes
return 0;
}

static void
foomodule_free(void *m) {
(void)foomodule_clear((PyObject *)m);
}

/* Implementation of publicly exported functions */

/*[clinic input]
module foo
[clinic start generated code]*/
/*[clinic end generated code: output=... input=...]*/

/*[clinic input]
foo.bar -> object

[clinic start generated code]*/
static PyObject *
foo_bar_impl(PyObject *module)
/*[clinic end generated code: output=... input=...]*/
{
return _Py_fast_bar();
}

/* Exported module's data */

static PyMethodDef foomodule_methods[] = {
FOO_BAR_METHODDEF // this becomes available after running 'make clinic'
{NULL, NULL}
};

static struct PyModuleDef_Slot foomodule_slots[] = {
{Py_mod_exec, foomodule_exec}, // foomodule_exec may be NULL if the state is trivial
{Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED},
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
{0, NULL},
};

static struct PyModuleDef foomodule = {
PyModuleDef_HEAD_INIT,
.m_name = "_foo",
.m_doc = "some doc", // or NULL if not needed
.m_size = sizeof(foomodule_state),
.m_methods = foomodule_methods,
.m_slots = foomodule_slots,
.m_traverse = foomodule_traverse, // or NULL if the state is trivial
.m_clear = foomodule_clear, // or NULL if the state is trivial
.m_free = foomodule_free, // or NULL if the state is trivial
};

PyMODINIT_FUNC
PyInit_foo(void)
{
return PyModuleDef_Init(&_foomodule);
}

In a separate file, we would have the implementation of ``Py_fast_bar``:

.. code-block:: c

// Modules/foo/helper.c

#include "foomodule.h"

PyObject *_Py_fast_bar() {
return PyUnicode_FromString("Hello World!");
}

Now, to summarize, we have the following files:

- ``Modules/foo/foomodule.h`` -- the shared prototypes for our mini-project.
- ``Modules/foo/foomodule.c`` -- the actual module's implementation.
- ``Modules/foo/helper.c`` -- some helper's implementation.

One could imagine having more ``.h`` files, or no ``helper.c`` file if it is
not needed. Here, we wanted to illustrate a simple example without making it
too trivial.

### Make the CPython project compile

Now that we have our files, we need to update the ``Makefile.pre.in`` file.
First, define the following the variables:

```makefile
FOO_H = Modules/foo/foomodule.h

FOO_OBJS = \
Modules/foo/foomodule.o \
Modules/foo/helper.o
```

and place them somewhere in the file (usually where other variables of the
same kind are).

Then, add the following rule in the '# Special rules for object files' section:

```makefile
$(FOO_OBJS): $(FOO_H)
```

and the following rule in the dependencies section:

```makefile
MODULE_FOO_DEPS=$(srcdir)/Modules/foo/foomodule.h
```

.. note::

The ``FOO_OBJS`` and ``FOO_H`` are not necessarily needed and the rule
``$(FOO_OBJS): $(FOO_H)`` could be hard-coded. Using Makefile variables
is generally better if more than multiple files need to be compiled.

Finally, we need to modify the configuration for Windows platforms:

- Open ``PC/config.c`` and add the prototype:

.. code-block:: c

extern PyObject* PyInit_foo(void);

and the entry ``{"foo", PyInit_foo}`` to ``_PyImport_Inittab``.

- Open ``PCbuild/pythoncore.vcxproj`` and add the following line to
the ``ItemGroup`` containing the ``..\Modules\*.h`` files:

.. code-block:: xml

<ClInclude Include="..\Modules\foo\foomodule.h" />

In addition, add the following lines to the ``ItemGroup`` containing
the ``..\Modules\*.c`` files:

.. code-block:: xml

picnixz marked this conversation as resolved.
Show resolved Hide resolved
<ClCompile Include="..\Modules\foo\foomodule.c" />
<ClCompile Include="..\Modules\foo\helper.c" />

- Open ``PCbuild/pythoncore.vcxproj.filters`` and add the following line to
the ``ItemGroup`` containing the ``..\Modules\*.h`` files:

.. code-block:: xml

<ClInclude Include="..\Modules\foo\foomodule.h">
<Filter>Modules\foo</Filter>
</ClInclude>

In addition, add the following lines to the ``ItemGroup`` containing
the ``..\Modules\*.c`` files:

.. code-block:: xml

<ClCompile Include="..\Modules\foo\foomodule.c">
<Filter>Modules\foo</Filter>
</ClCompile>
<ClCompile Include="..\Modules\foo\helper.c">
<Filter>Modules\foo</Filter>
</ClCompile>

Observe that ``.h`` files use ``<ClInclude ...>`` whereas ``.c`` files
use ``<ClCompile ...>`` tags.

### Compile the CPython project

Now that everything is in place, it remains to compile everything. To that
end, run the following commands:

.. code-block:: shell

make regen-configure
make regen-all
make regen-stdlib-module-names

.. tip:: Use ``make -j12`` to speed-up the compilation.

- The ``make regen-configure`` step regenerates the configure script.

- The ``make regen-all`` is responsible for running Arguments Clinic,
regenerating global objects, etc. It is useful to run when you do not
know which files should be updated.

- The ``regen-stdlib-module-names`` updates the standard module names,
making ``_foo`` discoverable and importable via ``import _foo``!

You can now compile the entire project by running the following commands:

.. code-block:: shell

./configure --with-pydebug
make

#### Troubleshooting: ``make regen-configure`` does not work!

Since this rule requires Docker to be running and a Docker instance,
the following can be done on Linux platforms (systemctl-based):
picnixz marked this conversation as resolved.
Show resolved Hide resolved

.. code-block:: shell

$ systemctl status docker # is the docker service running?
$ sudo systemctl start docker # start it if not!
$ sudo systemctl restart docker # or restart it!
picnixz marked this conversation as resolved.
Show resolved Hide resolved

If docker complains about missing permissions, the following StackOverflow post
could be useful in solving the issue: `How to fix docker: permission denied
<https://stackoverflow.com/q/48957195/9579194>`_
Loading
Morty Proxy This is a proxified and sanitized view of the page, visit original site.