Saltar a contenido

Última actualización:2026-10-12

Acelerar Python con pybind11

Normalmente, para las operaciones costosas esperamos implementarlas en C++ y luego llamar a las interfaces correspondientes desde Python, o bien, por culpa de la biblioteca subyacente, necesitamos dar soporte a una API Python externa; en definitiva, solemos necesitar acceder a C++ desde Python. ¿Cómo lograrlo?

Hay bastantes métodos; en esta sección tomamos pybind11 como ejemplo e introducimos una plantilla completa de proyecto de ingeniería, para que, si más adelante tienes esta necesidad, puedas modificarla partiendo de ella.

Nota: (versión para perezosos) todo el código de esta sección también está en la comunidad 星球.

1. Expansión de la macro

Supongamos: tomamos como ejemplo una ordenación relativamente costosa; si queremos escribir una ordenación en C++ y luego usarla en Python, ¿cómo escribirla?

El programa C++ es bastante sencillo: basta con exponer una interfaz que haga la ordenación, por ejemplo:

std::vector<int> sort_vector(const std::vector<int>& input) {
  std::vector<int> result = input;
  std::sort(result.begin(), result.end());
  return result;
}

Entonces surge la pregunta: para Pybind11, ¿cómo usar esta interfaz?

Basta usar la macro de pybind11 para definir un módulo; por ejemplo:

PYBIND11_MODULE(sort_module, m) {
    m.doc() = "pybind11 example plugin"; // Optional module docstring

    m.def("sort_vector", &sort_vector, "A function that sorts a list of integers");
}

Tras la expansión de la macro queda: PyInit_sort_module

Cuando en python hacemos import de la biblioteca xxx, el intérprete de Python carga un módulo de extensión escrito en C/C++ e invoca automáticamente la función de entrada, y esa función de entrada es justamente la PyInit_sort_module siguiente:

static ::pybind11::module_::module_def pybind11_module_def_sort_module [[maybe_unused]];

[[maybe_unused]] static void pybind11_init_sort_module(::pybind11::module_ &);

extern "C" [[maybe_unused]] __attribute__((visibility("default"))) PyObject *PyInit_sort_module();

extern "C" __attribute__((visibility("default"))) PyObject *PyInit_sort_module() {
    {
        const char *compiled_ver = "3" "." "11";
        const char *runtime_ver = Py_GetVersion();
        size_t len = std::strlen(compiled_ver);

        if (std::strncmp(runtime_ver, compiled_ver, len) != 0 || (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) {
            PyErr_Format(PyExc_ImportError,
                         "Python version mismatch: module was compiled for Python %s, "
                         "but the interpreter version is incompatible: %s.",
                         compiled_ver, runtime_ver);
            return nullptr;
        }
    }

    pybind11::detail::get_internals();
    auto m = ::pybind11::module_::create_extension_module("sort_module", nullptr, &pybind11_module_def_sort_module);

    try {
        pybind11_init_sort_module(m);
        return m.ptr();
    } catch (pybind11::error_already_set & e) {
        pybind11::raise_from(e, PyExc_ImportError, "initialization failed");
        return nullptr;
    } catch (const std::exception &e) {
        ::pybind11::set_error(PyExc_ImportError, e.what());
        return nullptr;
    }
}

void pybind11_init_sort_module(::pybind11::module_ & m) {
    m.doc() = "pybind11 example plugin";

    m.def("sort_vector", &sort_vector, "A function that sorts a list of integers");
}

Por eso, si no usamos esta macro, al hacer import de la biblioteca obtendremos un error:

>>> import sort_module
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: dynamic module does not define module export function (PyInit_sort_module)

2. CMake + pybind11

El uso de pybind11 en C++ es el siguiente:

  • Archivos de cabecera
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
  • Macro
PYBIND11_MODULE(sort_module, m) {
    // 你的实现
}

¿Y cómo descargar y enlazar pybind11 en cmake?

Basta con usar el módulo FetchContent; por ejemplo:

FetchContent_Declare(
    pybind11
    URL https://github.com/pybind/pybind11/archive/v2.12.0.tar.gz
    )

Después añade tu módulo en el CMakeLists:

pybind11_add_module(sort_module sort_module.cc)

cmake descargará pybingd11 por ti y además lo dejará compilado.

3. Uso

Lo anterior genera un so; instálalo en el directorio site-packages de python y, al hacer import, se reconocerá. A continuación se demuestra nuestro módulo personalizado:

import sort_module

def test_sort():
    input_list = [5, 2, 3, 1, 4]
    sorted_list = sort_module.sort_vector(input_list)
    print(f"Original list: {input_list}")
    print(f"Sorted list: {sorted_list}")

if __name__ == "__main__":
    test_sort()

Ejecución:

(base) ➜  build python ../test.py
Original list: [5, 2, 3, 1, 4]
Sorted list: [1, 2, 3, 4, 5]

Temas relacionados

Comentarios