mirror of
https://github.com/python/cpython.git
synced 2026-01-20 14:20:27 +00:00
Co-authored-by: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Co-authored-by: Éric <merwok@netwok.org> Co-authored-by: Daniele Nicolodi <daniele@grinta.net> Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
667 lines
20 KiB
ReStructuredText
667 lines
20 KiB
ReStructuredText
.. highlight:: c
|
|
|
|
|
|
.. _extending-simpleexample:
|
|
.. _first-extension-module:
|
|
|
|
*********************************
|
|
Your first C API extension module
|
|
*********************************
|
|
|
|
This tutorial will take you through creating a simple
|
|
Python extension module written in C or C++.
|
|
|
|
We will use the low-level Python C API directly.
|
|
For easier ways to create extension modules, see
|
|
the :ref:`recommended third party tools <c-api-tools>`.
|
|
|
|
The tutorial assumes basic knowledge about Python: you should be able to
|
|
define functions in Python code before starting to write them in C.
|
|
See :ref:`tutorial-index` for an introduction to Python itself.
|
|
|
|
The tutorial should be approachable for anyone who can write a basic C library.
|
|
While we will mention several concepts that a C beginner would not be expected
|
|
to know, like ``static`` functions or linkage declarations, understanding these
|
|
is not necessary for success.
|
|
|
|
We will focus on giving you a "feel" of what Python's C API is like.
|
|
It will not teach you important concepts, like error handling
|
|
and reference counting, which are covered in later chapters.
|
|
|
|
We will assume that you use a Unix-like system (including macOS and
|
|
Linux), or Windows.
|
|
On other systems, you might need to adjust some details -- for example,
|
|
a system command name.
|
|
|
|
You need to have a suitable C compiler and Python development headers installed.
|
|
On Linux, headers are often in a package like ``python3-dev``
|
|
or ``python3-devel``.
|
|
|
|
You need to be able to install Python packages.
|
|
This tutorial uses `pip <https://pip.pypa.io/>`__ (``pip install``), but you
|
|
can substitute any tool that can build and install ``pyproject.toml``-based
|
|
projects, like `uv <https://docs.astral.sh/uv/>`_ (``uv pip install``).
|
|
Preferably, have a :ref:`virtual environment <venv-def>` activated.
|
|
|
|
|
|
.. note::
|
|
|
|
This tutorial uses APIs that were added in CPython 3.15.
|
|
To create an extension that's compatible with earlier versions of CPython,
|
|
please follow an earlier version of this documentation.
|
|
|
|
This tutorial uses C syntax added in C11 and C++20.
|
|
If your extension needs to be compatible with earlier standards,
|
|
please follow tutorials in documentation for Python 3.14 or below.
|
|
|
|
|
|
What we'll do
|
|
=============
|
|
|
|
Let's create an extension module called ``spam`` [#why-spam]_,
|
|
which will include a Python interface to the C
|
|
standard library function :c:func:`system`.
|
|
This function is defined in ``stdlib.h``.
|
|
It takes a C string as argument, runs the argument as a system
|
|
command, and returns a result value as an integer.
|
|
A manual page for :c:func:`system` might summarize it this way::
|
|
|
|
#include <stdlib.h>
|
|
int system(const char *command);
|
|
|
|
Note that like many functions in the C standard library,
|
|
this function is already exposed in Python.
|
|
In production, use :py:func:`os.system` or :py:func:`subprocess.run`
|
|
rather than the module you'll write here.
|
|
|
|
We want this function to be callable from Python as follows:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> status = spam.system("whoami")
|
|
User Name
|
|
>>> status
|
|
0
|
|
|
|
.. note::
|
|
|
|
The system command ``whoami`` prints out your username.
|
|
It's useful in tutorials like this one because it has the same name on
|
|
both Unix and Windows.
|
|
|
|
|
|
Start with the headers
|
|
======================
|
|
|
|
Begin by creating a directory for this tutorial, and switching to it
|
|
on the command line.
|
|
Then, create a file named :file:`spammodule.c` in your directory.
|
|
[#why-spammodule]_
|
|
|
|
In this file, we'll include two headers: :file:`Python.h` to pull in
|
|
all declarations of the Python C API, and :file:`stdlib.h` for the
|
|
:c:func:`system` function. [#stdlib-h]_
|
|
|
|
Add the following lines to :file:`spammodule.c`:
|
|
|
|
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
|
|
:start-at: <Python.h>
|
|
:end-at: <stdlib.h>
|
|
|
|
Be sure to put :file:`stdlib.h`, and any other standard library includes,
|
|
*after* :file:`Python.h`.
|
|
On some systems, Python may define some pre-processor definitions
|
|
that affect the standard headers.
|
|
|
|
|
|
Running your build tool
|
|
=======================
|
|
|
|
With only the includes in place, your extension won't do anything.
|
|
Still, it's a good time to compile it and try to import it.
|
|
This will ensure that your build tool works, so that you can make
|
|
and test incremental changes as you follow the rest of the text.
|
|
|
|
CPython itself does not come with a tool to build extension modules;
|
|
it is recommended to use a third-party project for this.
|
|
In this tutorial, we'll use `meson-python`_.
|
|
(If you want to use another one, see :ref:`first-extension-other-tools`.)
|
|
|
|
.. at the time of writing, meson-python has the least overhead for a
|
|
simple extension using PyModExport.
|
|
Change this if another tool makes things easier.
|
|
|
|
``meson-python`` requires defining a "project" using two extra files.
|
|
|
|
First, add ``pyproject.toml`` with these contents:
|
|
|
|
.. code-block:: toml
|
|
|
|
[build-system]
|
|
build-backend = 'mesonpy'
|
|
requires = ['meson-python']
|
|
|
|
[project]
|
|
# Placeholder project information
|
|
# (change this before distributing the module)
|
|
name = 'sampleproject'
|
|
version = '0'
|
|
|
|
Then, create ``meson.build`` containing the following:
|
|
|
|
.. code-block:: meson
|
|
|
|
project('sampleproject', 'c')
|
|
|
|
py = import('python').find_installation(pure: false)
|
|
|
|
py.extension_module(
|
|
'spam', # name of the importable Python module
|
|
'spammodule.c', # the C source file
|
|
install: true,
|
|
)
|
|
|
|
.. note::
|
|
|
|
See `meson-python documentation <meson-python>`_ for details on
|
|
configuration.
|
|
|
|
Now, build install the *project in the current directory* (``.``) via ``pip``:
|
|
|
|
.. code-block:: sh
|
|
|
|
python -m pip install .
|
|
|
|
.. tip::
|
|
|
|
If you don't have ``pip`` installed, run ``python -m ensurepip``,
|
|
preferably in a :ref:`virtual environment <venv-def>`.
|
|
(Or, if you prefer another tool that can build and install
|
|
``pyproject.toml``-based projects, use that.)
|
|
|
|
.. _meson-python: https://mesonbuild.com/meson-python/
|
|
.. _virtual environment: https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments
|
|
|
|
Note that you will need to run this command again every time you change your
|
|
extension.
|
|
Unlike Python, C has an explicit compilation step.
|
|
|
|
When your extension is compiled and installed, start Python and try to
|
|
import it.
|
|
This should fail with the following exception:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
Traceback (most recent call last):
|
|
...
|
|
ImportError: dynamic module does not define module export function (PyModExport_spam or PyInit_spam)
|
|
|
|
|
|
Module export hook
|
|
==================
|
|
|
|
The exception you got when you tried to import the module told you that Python
|
|
is looking for a "module export function", also known as a
|
|
:ref:`module export hook <extension-export-hook>`.
|
|
Let's define one.
|
|
|
|
First, add a prototype below the ``#include`` lines:
|
|
|
|
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
|
|
:start-after: /// Export hook prototype
|
|
:end-before: ///
|
|
|
|
.. tip::
|
|
The prototype is not strictly necessary, but some modern compilers emit
|
|
warnings without it.
|
|
It's generally better to add the prototype than to disable the warning.
|
|
|
|
The :c:macro:`PyMODEXPORT_FUNC` macro declares the function's
|
|
return type, and adds any special linkage declarations needed
|
|
to make the function visible and usable when CPython loads it.
|
|
|
|
After the prototype, add the function itself.
|
|
For now, make it return ``NULL``:
|
|
|
|
.. code-block:: c
|
|
|
|
PyMODEXPORT_FUNC
|
|
PyModExport_spam(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
Compile and load the module again.
|
|
You should get a different error this time.
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
Traceback (most recent call last):
|
|
...
|
|
SystemError: module export hook for module 'spam' failed without setting an exception
|
|
|
|
Simply returning ``NULL`` is *not* correct behavior for an export hook,
|
|
and CPython complains about it.
|
|
That's good -- it means that CPython found the function!
|
|
Let's now make it do something useful.
|
|
|
|
|
|
The slot table
|
|
==============
|
|
|
|
Rather than ``NULL``, the export hook should return the information needed to
|
|
create a module.
|
|
Let's start with the basics: the name and docstring.
|
|
|
|
The information should be defined in a ``static`` array of
|
|
:c:type:`PyModuleDef_Slot` entries, which are essentially key-value pairs.
|
|
Define this array just before your export hook:
|
|
|
|
.. code-block:: c
|
|
|
|
static PyModuleDef_Slot spam_slots[] = {
|
|
{Py_mod_name, "spam"},
|
|
{Py_mod_doc, "A wonderful module with an example function"},
|
|
{0, NULL}
|
|
};
|
|
|
|
For both :c:data:`Py_mod_name` and :c:data:`Py_mod_doc`, the values are C
|
|
strings -- that is, NUL-terminated, UTF-8 encoded byte arrays.
|
|
|
|
Note the zero-filled sentinel entry at the end.
|
|
If you forget it, you'll trigger undefined behavior.
|
|
|
|
The array is defined as ``static`` -- that is, not visible outside this ``.c`` file.
|
|
This will be a common theme.
|
|
CPython only needs to access the export hook; all global variables
|
|
and all other functions should generally be ``static``, so that they don't
|
|
clash with other extensions.
|
|
|
|
Return this array from your export hook instead of ``NULL``:
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 4
|
|
|
|
PyMODEXPORT_FUNC
|
|
PyModExport_spam(void)
|
|
{
|
|
return spam_slots;
|
|
}
|
|
|
|
Now, recompile and try it out:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> print(spam)
|
|
<module 'spam' from '/home/encukou/dev/cpython/spam.so'>
|
|
|
|
You have an extension module!
|
|
Try ``help(spam)`` to see the docstring.
|
|
|
|
The next step will be adding a function.
|
|
|
|
|
|
.. _backtoexample:
|
|
|
|
Exposing a function
|
|
===================
|
|
|
|
To expose the :c:func:`system` C function directly to Python,
|
|
we'll need to write a layer of glue code to convert arguments from Python
|
|
objects to C values, and the C return value back to Python.
|
|
|
|
One of the simplest ways to write glue code is a ":c:data:`METH_O`" function,
|
|
which takes two Python objects and returns one.
|
|
All Python objects -- regardless of the Python type -- are represented in C
|
|
as pointers to the :c:type:`PyObject` structure.
|
|
|
|
Add such a function above the slots array::
|
|
|
|
static PyObject *
|
|
spam_system(PyObject *self, PyObject *arg)
|
|
{
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
For now, we ignore the arguments, and use the :c:macro:`Py_RETURN_NONE`
|
|
macro, which expands to a ``return`` statement that properly returns
|
|
a Python :py:data:`None` object.
|
|
|
|
Recompile your extension to make sure you don't have syntax errors.
|
|
We haven't yet added ``spam_system`` to the module, so you might get a
|
|
warning that ``spam_system`` is unused.
|
|
|
|
.. _methodtable:
|
|
|
|
Method definitions
|
|
------------------
|
|
|
|
To expose the C function to Python, you will need to provide several pieces of
|
|
information in a structure called
|
|
:c:type:`PyMethodDef` [#why-pymethoddef]_:
|
|
|
|
* ``ml_name``: the name of the Python function;
|
|
* ``ml_doc``: a docstring;
|
|
* ``ml_meth``: the C function to be called; and
|
|
* ``ml_flags``: a set of flags describing details like how Python arguments are
|
|
passed to the C function.
|
|
We'll use :c:data:`METH_O` here -- the flag that matches our
|
|
``spam_system`` function's signature.
|
|
|
|
Because modules typically create several functions, these definitions
|
|
need to be collected in an array, with a zero-filled sentinel at the end.
|
|
Add this array just below the ``spam_system`` function:
|
|
|
|
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
|
|
:start-after: /// Module method table
|
|
:end-before: ///
|
|
|
|
As with module slots, a zero-filled sentinel marks the end of the array.
|
|
|
|
Next, we'll add the method to the module.
|
|
Add a :c:data:`Py_mod_methods` slot to your :c:type:`PyMethodDef` array:
|
|
|
|
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
|
|
:start-after: /// Module slot table
|
|
:end-before: ///
|
|
:emphasize-lines: 5
|
|
|
|
Recompile your extension again, and test it.
|
|
Be sure to restart the Python interpreter, so that ``import spam`` picks
|
|
up the new version of the module.
|
|
|
|
You should now be able to call the function:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> print(spam.system)
|
|
<built-in function system>
|
|
>>> print(spam.system('whoami'))
|
|
None
|
|
|
|
Note that our ``spam.system`` does not yet run the ``whoami`` command;
|
|
it only returns ``None``.
|
|
|
|
Check that the function accepts exactly one argument, as specified by
|
|
the :c:data:`METH_O` flag:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> print(spam.system('too', 'many', 'arguments'))
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: spam.system() takes exactly one argument (3 given)
|
|
|
|
|
|
Returning an integer
|
|
====================
|
|
|
|
Now, let's take a look at the return value.
|
|
Instead of ``None``, we'll want ``spam.system`` to return a number -- that is,
|
|
a Python :py:type:`int` object.
|
|
Eventually this will be the exit code of a system command,
|
|
but let's start with a fixed value, say, ``3``.
|
|
|
|
The Python C API provides a function to create a Python :py:type:`int` object
|
|
from a C ``int`` value: :c:func:`PyLong_FromLong`. [#why-pylongfromlong]_
|
|
|
|
To call it, replace the ``Py_RETURN_NONE`` with the following 3 lines:
|
|
|
|
.. this could be a one-liner, but we want to show the data types here
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 4-6
|
|
|
|
static PyObject *
|
|
spam_system(PyObject *self, PyObject *arg)
|
|
{
|
|
int status = 3;
|
|
PyObject *result = PyLong_FromLong(status);
|
|
return result;
|
|
}
|
|
|
|
|
|
Recompile, restart the Python interpreter again,
|
|
and check that the function now returns 3:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> spam.system('whoami')
|
|
3
|
|
|
|
|
|
Accepting a string
|
|
==================
|
|
|
|
Finally, let's handle the function argument.
|
|
|
|
Our C function, :c:func:`!spam_system`, takes two arguments.
|
|
The first one, ``PyObject *self``, will be set to the ``spam`` module
|
|
object.
|
|
This isn't useful in our case, so we'll ignore it.
|
|
|
|
The other one, ``PyObject *arg``, will be set to the object that the user
|
|
passed from Python.
|
|
We expect that it should be a Python string.
|
|
In order to use the information in it, we will need
|
|
to convert it to a C value -- in this case, a C string (``const char *``).
|
|
|
|
There's a slight type mismatch here: Python's :py:class:`str` objects store
|
|
Unicode text, but C strings are arrays of bytes.
|
|
So, we'll need to *encode* the data, and we'll use the UTF-8 encoding for it.
|
|
(UTF-8 might not always be correct for system commands, but it's what
|
|
:py:meth:`str.encode` uses by default,
|
|
and the C API has special support for it.)
|
|
|
|
The function to encode a Python string into a UTF-8 buffer is named
|
|
:c:func:`PyUnicode_AsUTF8` [#why-pyunicodeasutf8]_.
|
|
Call it like this:
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 4
|
|
|
|
static PyObject *
|
|
spam_system(PyObject *self, PyObject *arg)
|
|
{
|
|
const char *command = PyUnicode_AsUTF8(arg);
|
|
int status = 3;
|
|
PyObject *result = PyLong_FromLong(status);
|
|
return result;
|
|
}
|
|
|
|
If :c:func:`PyUnicode_AsUTF8` is successful, *command* will point to the
|
|
resulting array of bytes.
|
|
This buffer is managed by the *arg* object, which means we don't need to free
|
|
it, but we must follow some rules:
|
|
|
|
* We should only use the buffer inside the ``spam_system`` function.
|
|
When ``spam_system`` returns, *arg* and the buffer it manages might be
|
|
garbage-collected.
|
|
* We must not modify it. This is why we use ``const``.
|
|
|
|
If :c:func:`PyUnicode_AsUTF8` was *not* successful, it returns a ``NULL``
|
|
pointer.
|
|
When calling *any* Python C API, we always need to handle such error cases.
|
|
The way to do this in general is left for later chapters of this documentation.
|
|
For now, be assured that we are already handling errors from
|
|
:c:func:`PyLong_FromLong` correctly.
|
|
|
|
For the :c:func:`PyUnicode_AsUTF8` call, the correct way to handle errors is
|
|
returning ``NULL`` from ``spam_system``.
|
|
Add an ``if`` block for this:
|
|
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 5-7
|
|
|
|
static PyObject *
|
|
spam_system(PyObject *self, PyObject *arg)
|
|
{
|
|
const char *command = PyUnicode_AsUTF8(arg);
|
|
if (command == NULL) {
|
|
return NULL;
|
|
}
|
|
int status = 3;
|
|
PyObject *result = PyLong_FromLong(status);
|
|
return result;
|
|
}
|
|
|
|
That's it for the setup.
|
|
Now, all that is left is calling the C library function :c:func:`system` with
|
|
the ``char *`` buffer, and using its result instead of the ``3``:
|
|
|
|
.. code-block:: c
|
|
:emphasize-lines: 8
|
|
|
|
static PyObject *
|
|
spam_system(PyObject *self, PyObject *arg)
|
|
{
|
|
const char *command = PyUnicode_AsUTF8(arg);
|
|
if (command == NULL) {
|
|
return NULL;
|
|
}
|
|
int status = system(command);
|
|
PyObject *result = PyLong_FromLong(status);
|
|
return result;
|
|
}
|
|
|
|
Compile your module, restart Python, and test.
|
|
This time, you should see your username -- the output of the ``whoami``
|
|
system command:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> result = spam.system('whoami')
|
|
User Name
|
|
>>> result
|
|
0
|
|
|
|
You might also want to test error cases:
|
|
|
|
.. code-block:: pycon
|
|
|
|
>>> import spam
|
|
>>> result = spam.system('nonexistent-command')
|
|
sh: line 1: nonexistent-command: command not found
|
|
>>> result
|
|
32512
|
|
|
|
>>> spam.system(3)
|
|
Traceback (most recent call last):
|
|
...
|
|
TypeError: bad argument type for built-in operation
|
|
|
|
|
|
The result
|
|
==========
|
|
|
|
|
|
Congratulations!
|
|
You have written a complete Python C API extension module,
|
|
and completed this tutorial!
|
|
|
|
Here is the entire source file, for your convenience:
|
|
|
|
.. _extending-spammodule-source:
|
|
|
|
.. literalinclude:: ../includes/capi-extension/spammodule-01.c
|
|
:start-at: ///
|
|
|
|
|
|
.. _first-extension-other-tools:
|
|
|
|
Appendix: Other build tools
|
|
===========================
|
|
|
|
You should be able to follow this tutorial -- except the
|
|
*Running your build tool* section itself -- with a build tool other
|
|
than ``meson-python``.
|
|
|
|
The Python Packaging User Guide has a `list of recommended tools <https://packaging.python.org/en/latest/guides/tool-recommendations/#build-backends-for-extension-modules>`_;
|
|
be sure to choose one for the C language.
|
|
|
|
|
|
Workaround for missing PyInit function
|
|
--------------------------------------
|
|
|
|
If your build tool output complains about missing ``PyInit_spam``,
|
|
add the following function to your module for now:
|
|
|
|
.. code-block:: c
|
|
|
|
// A workaround
|
|
void *PyInit_spam(void) { return NULL; }
|
|
|
|
This is a shim for an old-style :ref:`initialization function <extension-export-hook>`,
|
|
which was required in extension modules for CPython 3.14 and below.
|
|
Current CPython does not need it, but some build tools may still assume that
|
|
all extension modules need to define it.
|
|
|
|
If you use this workaround, you will get the exception
|
|
``SystemError: initialization of spam failed without raising an exception``
|
|
instead of
|
|
``ImportError: dynamic module does not define module export function``.
|
|
|
|
|
|
Compiling directly
|
|
------------------
|
|
|
|
Using a third-party build tool is heavily recommended,
|
|
as it will take care of various details of your platform and Python
|
|
installation, of naming the resulting extension, and, later, of distributing
|
|
your work.
|
|
|
|
If you are building an extension for as *specific* system, or for yourself
|
|
only, you might instead want to run your compiler directly.
|
|
The way to do this is system-specific; be prepared for issues you will need
|
|
to solve yourself.
|
|
|
|
Linux
|
|
^^^^^
|
|
|
|
On Linux, the Python development package may include a ``python3-config``
|
|
command that prints out the required compiler flags.
|
|
If you use it, check that it corresponds to the CPython interpreter you'll use
|
|
to load the module.
|
|
Then, start with the following command:
|
|
|
|
.. code-block:: sh
|
|
|
|
gcc --shared $(python3-config --cflags --ldflags) spammodule.c -o spam.so
|
|
|
|
This should generate a ``spam.so`` file that you need to put in a directory
|
|
on :py:attr:`sys.path`.
|
|
|
|
|
|
.. rubric:: Footnotes
|
|
|
|
.. [#why-spam] ``spam`` is the favorite food of Monty Python fans...
|
|
.. [#why-spammodule] The source file name is entirely up to you,
|
|
though some tools can be picky about the ``.c`` extension.
|
|
This tutorial uses the traditional ``*module.c`` suffix.
|
|
Some people would just use :file:`spam.c` to implement a module
|
|
named ``spam``,
|
|
projects where Python isn't the primary language might use ``py_spam.c``,
|
|
and so on.
|
|
.. [#stdlib-h] Including :file:`stdlib.h` is technically not necessary,
|
|
since :file:`Python.h` includes it and
|
|
:ref:`several other standard headers <capi-system-includes>` for its own use
|
|
or for backwards compatibility.
|
|
However, it is good practice to explicitly include what you need.
|
|
.. [#why-pymethoddef] The :c:type:`!PyMethodDef` structure is also used
|
|
to create methods of classes, so there's no separate
|
|
":c:type:`!PyFunctionDef`".
|
|
.. [#why-pylongfromlong] The name :c:func:`PyLong_FromLong`
|
|
might not seem obvious.
|
|
``PyLong`` refers to a the Python :py:class:`int`, which was originally
|
|
called ``long``; the ``FromLong`` refers to the C ``long`` (or ``long int``)
|
|
type.
|
|
.. [#why-pyunicodeasutf8] Here, ``PyUnicode`` refers to the original name of
|
|
the Python :py:class:`str` class: ``unicode``.
|