Inline C dans une fonction Python

Parce que, je voulais voir où ça mènerai, bah ça mène là (pour Linux uniquement) :

import shlex
import tempfile
from ctypes import CDLL, py_object
from functools import wraps
from inspect import signature
from subprocess import PIPE, run


def compile(c_str) -> CDLL:
    with tempfile.TemporaryDirectory() as tmpdir:
        with open(f"{tmpdir}/tmp.c", "w", encoding="UTF-8") as f:
            f.write(c_str)
        flags = shlex.split(run(["python3-config", "--cflags", "--ldflags"], encoding="UTF-8", stdout=PIPE).stdout)
        run(["cc", "-shared", "-fPIC", "-xc"] + flags + [f"-o{tmpdir}/tmp.so", f"{tmpdir}/tmp.c"], check=True)
        return CDLL(f"{tmpdir}/tmp.so")


def c(fct):
    parameters = signature(fct).parameters
    c_args = ", ".join([f"PyObject *{name}" for name in parameters.keys()])
    lib = compile(FR"""
#include <stdio.h>
#include <Python.h>

PyObject *the_function({c_args}) {{
    {fct.__doc__}
}}
""")
    lib.the_function.argtypes = [py_object] * len(parameters)
    lib.the_function.restype = py_object
    @wraps(fct)
    def call_c(*args):
        return lib.the_function(*args)
    return call_c


@c
def cprint(pyobject):
    r"""
    Py_ssize_t size;
    const char *str;

    PyGILState_STATE state = PyGILState_Ensure();
    if (PyUnicode_CheckExact(pyobject)) {
        str = PyUnicode_AsUTF8AndSize(pyobject, &size);
        write(1, str, size);
        write(1, "\n", 1);
    }
    else {
        PyObject *repr = PyObject_Repr(pyobject);
        printf("%s\n", PyUnicode_AsUTF8(repr));
    }
    PyGILState_Release(state);
    return Py_None;
    """


@c
def fib(n):
    """
    long m = PyLong_AsLong(n);
    long a = 1;
    long b = 1;

    PyGILState_STATE state = PyGILState_Ensure();
    for (int i = 0; i < m; i++) {
        b = a + b;
        a = b - a;
    }
    n = PyLong_FromLong(a);
    PyGILState_Release(state);
    return n;
    """


def main():
    cprint([fib(i) for i in range(40)])


if __name__ == "__main__":
    main()

Bon ça marche, ça m’amuse, c’est tout.

OK pour ceux qui veulent vraiment parler perfs, même si c’était pas mon but (promis) :

$ pyperf timeit -s 'import inline_c' 'inline_c.py_fib(40)'
.....................
Mean +- std dev: 993 ns +- 70 ns

$ pyperf timeit -s 'import inline_c' 'inline_c.c_fib(40)'
.....................
Mean +- std dev: 257 ns +- 6 ns

mais on perd la magie des int de Python, très vite le long ne suffira pas a contenir le résultat de fib, les deux implèms ne sont donc pas comparables, l’une est rapide, l’autre est juste, meh, arrêtons de parler perfs.

Qui pour implémenter @asm ?

3 « J'aime »