¿Llamando a C / C ++ desde Python?


521

¿Cuál sería la forma más rápida de construir un enlace de Python a una biblioteca C o C ++?

(Estoy usando Windows si esto es importante).

Respuestas:


170

Deberías echar un vistazo a Boost.Python . Aquí está la breve introducción tomada de su sitio web:

Boost Python Library es un marco para la interfaz de Python y C ++. Le permite exponer rápida y sin problemas las funciones y objetos de las clases de C ++ a Python, y viceversa, sin utilizar herramientas especiales, solo su compilador de C ++. Está diseñado para envolver las interfaces de C ++ de manera no intrusiva, por lo que no debería tener que cambiar el código de C ++ para envolverlo, lo que hace que Boost.Python sea ideal para exponer bibliotecas de terceros a Python. El uso de la biblioteca de técnicas avanzadas de metaprogramación simplifica su sintaxis para los usuarios, de modo que el código de ajuste adopta la apariencia de un tipo de lenguaje de definición de interfaz declarativa (IDL).


Boost.Python es una de las bibliotecas más fáciles de usar en Boost, para una simple función llamada API, es bastante sencillo y proporciona repeticiones que tendría que escribir usted mismo. Es un poco más complicado si quieres exponer una API orientada a objetos.
jwfearn

15
Boost.Python es lo peor que se pueda imaginar. Para cada máquina nueva y con cada actualización va con problemas de vinculación.
Miller

14
¿Casi 11 años después, para contemplar la calidad de esta respuesta?
J Evans

44
¿Sigue siendo el mejor enfoque para la interfaz de Python y C ++?
tushaR

8
Tal vez puedas probar pybind11, que es liviano en comparación con boost.
jdhao

659

El módulo ctypes es parte de la biblioteca estándar y, por lo tanto, es más estable y está más disponible que Swig , lo que siempre me causó problemas .

Con ctypes, debe satisfacer cualquier dependencia del tiempo de compilación en python, y su enlace funcionará en cualquier python que tenga ctypes, no solo en el que se compiló.

Suponga que tiene una clase de ejemplo C ++ simple con la que desea hablar en un archivo llamado foo.cpp:

#include <iostream>

class Foo{
    public:
        void bar(){
            std::cout << "Hello" << std::endl;
        }
};

Dado que los ctypes solo pueden comunicarse con las funciones de C, debe proporcionar a aquellos que las declaran como "C" externas

extern "C" {
    Foo* Foo_new(){ return new Foo(); }
    void Foo_bar(Foo* foo){ foo->bar(); }
}

Luego debes compilar esto en una biblioteca compartida

g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

Y finalmente tienes que escribir tu envoltorio de python (por ejemplo, en fooWrapper.py)

from ctypes import cdll
lib = cdll.LoadLibrary('./libfoo.so')

class Foo(object):
    def __init__(self):
        self.obj = lib.Foo_new()

    def bar(self):
        lib.Foo_bar(self.obj)

Una vez que tenga eso, puede llamarlo así

f = Foo()
f.bar() #and you will see "Hello" on the screen

14
Esto es más o menos lo que boost.python hace por usted en una sola llamada de función.
Martin Beckett

203
ctypes está en la biblioteca estándar de python, swig y boost no. Swig y boost dependen de módulos de extensión y, por lo tanto, están vinculados a versiones menores de Python que los objetos compartidos independientes no tienen. construir un swig o envoltorios de refuerzo puede ser un dolor, ctypes no requiere requisitos de construcción
Florian Bösch

25
boost se basa en la plantilla de vudú mágica y en un sistema de construcción completamente personalizado, ctypes se basa en la simplicidad. ctypes es dinámico, boost es estático. Los ctypes pueden manejar diferentes versiones de bibliotecas. impulso no puede.
Florian Bösch

32
En Windows, tuve que especificar __declspec (dllexport) en mis firmas de funciones para que Python pueda verlas. Del ejemplo anterior esto correspondería a: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald

13
No olvide eliminar el puntero después, por ejemplo, proporcionando una Foo_deletefunción y llamándola desde un destructor de python o envolviendo el objeto en un recurso .
Adversus

58

La forma más rápida de hacer esto es usar SWIG .

Ejemplo del tutorial SWIG :

/* File : example.c */
int fact(int n) {
    if (n <= 1) return 1;
    else return n*fact(n-1);
}

Archivo de interfaz:

/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}

extern int fact(int n);

Construyendo un módulo Python en Unix:

swig -python example.i
gcc -fPIC -c example.c example_wrap.c -I/usr/local/include/python2.7
gcc -shared example.o example_wrap.o -o _example.so

Uso:

>>> import example
>>> example.fact(5)
120

Tenga en cuenta que debe tener python-dev. Además, en algunos sistemas, los archivos de encabezado de Python estarán en /usr/include/python2.7 según la forma en que lo haya instalado.

Del tutorial:

SWIG es un compilador de C ++ bastante completo con soporte para casi todas las funciones de lenguaje. Esto incluye preprocesamiento, punteros, clases, herencia e incluso plantillas de C ++. SWIG también se puede utilizar para empaquetar estructuras y clases en clases proxy en el idioma de destino, exponiendo la funcionalidad subyacente de una manera muy natural.


50

Comencé mi viaje en el enlace Python <-> C ++ desde esta página, con el objetivo de vincular tipos de datos de alto nivel (vectores STL multidimensionales con listas de Python) :-)

Después de haber probado las soluciones basadas en ctypes y boost.python (y no ser un ingeniero de software), las he encontrado complejas cuando se requiere un enlace de tipos de datos de alto nivel, mientras que he encontrado SWIG es mucho más simple para tales casos.

Este ejemplo utiliza, por lo tanto, SWIG, y se ha probado en Linux (pero SWIG está disponible y también se usa ampliamente en Windows).

El objetivo es poner a disposición de Python una función C ++ que tome una matriz en forma de un vector STL 2D y devuelva un promedio de cada fila (como un vector STD 1D).

El código en C ++ ("code.cpp") es el siguiente:

#include <vector>
#include "code.h"

using namespace std;

vector<double> average (vector< vector<double> > i_matrix) {

  // Compute average of each row..
  vector <double> averages;
  for (int r = 0; r < i_matrix.size(); r++){
    double rsum = 0.0;
    double ncols= i_matrix[r].size();
    for (int c = 0; c< i_matrix[r].size(); c++){
      rsum += i_matrix[r][c];
    }
    averages.push_back(rsum/ncols);
  }
  return averages;
}

El encabezado equivalente ("code.h") es:

#ifndef _code
#define _code

#include <vector>

std::vector<double> average (std::vector< std::vector<double> > i_matrix);

#endif

Primero compilamos el código C ++ para crear un archivo objeto:

g++ -c -fPIC code.cpp

Luego definimos un archivo de definición de interfaz SWIG ("code.i") para nuestras funciones C ++.

%module code
%{
#include "code.h"
%}
%include "std_vector.i"
namespace std {

  /* On a side note, the names VecDouble and VecVecdouble can be changed, but the order of first the inner vector matters! */
  %template(VecDouble) vector<double>;
  %template(VecVecdouble) vector< vector<double> >;
}

%include "code.h"

Usando SWIG, generamos un código fuente de interfaz C ++ a partir del archivo de definición de interfaz SWIG.

swig -c++ -python code.i

Finalmente compilamos el archivo fuente de la interfaz C ++ generada y enlazamos todo para generar una biblioteca compartida que Python puede importar directamente (el "_" importa):

g++ -c -fPIC code_wrap.cxx  -I/usr/include/python2.7 -I/usr/lib/python2.7
g++ -shared -Wl,-soname,_code.so -o _code.so code.o code_wrap.o

Ahora podemos usar la función en scripts de Python:

#!/usr/bin/env python

import code
a= [[3,5,7],[8,10,12]]
print a
b = code.average(a)
print "Assignment done"
print a
print b

Una implementación de caso real donde en el código C ++ los vectores stl se pasan como referencias no constantes y, por lo tanto, están disponibles por python como parámetros de salida: lobianco.org/antonello/personal:portfolio:portopt
Antonello

33

También existe pybind11, que es como una versión ligera de Boost.Python y compatible con todos los compiladores de C ++ modernos:

https://pybind11.readthedocs.io/en/latest/


1
Hoy !! 2020 ¡ Esta debería ser la mejor respuesta! Es una biblioteca de solo encabezado de plantilla. Muchos grandes proyectos relevantes lo recomiendan, como Pytorch pytorch.org/tutorials/advanced/cpp_extension.html También funciona completamente en VS CommunityWindows
eusoubrasileiro

30

Echa un vistazo a pyrex o Cython . Son lenguajes similares a Python para la interfaz entre C / C ++ y Python.


1
+1 para Cython! No he probado cffi, así que no puedo decir cuál es mejor, pero tuve muy buenas experiencias con Cython: todavía está escribiendo código Python pero puede usar C en él. Fue algo difícil para mí configurar el proceso de compilación con Cython, que luego expliqué en una publicación de blog: martinsosic.com/development/2016/02/08/…
Martinsos

Es posible que desee mejorar la respuesta para que ya no sea una respuesta de solo enlace.
Adelin

He estado usando Cython durante aproximadamente una semana y me gusta mucho: 1) He visto ctypes en uso y es feo y muy propenso a errores con numerosas trampas 2) Te permite tomar un código de Python y acelerarlo de escribir cosas estáticamente solo 3) Es sencillo escribir envoltorios de Python para métodos y objetos C / C ++ 4) Todavía está bien soportado. Se podría hacer con más orientación con respecto a la instalación en venvs y la compilación cruzada, lo que ha llevado un poco de tiempo resolverlo. Aquí hay un video tutorial muy bueno de 4 horas: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason el

22

Para C ++ moderno, use cppyy: http://cppyy.readthedocs.io/en/latest/

Está basado en Cling, el intérprete de C ++ para Clang / LLVM. Los enlaces están en tiempo de ejecución y no es necesario un lenguaje intermedio adicional. Gracias a Clang, es compatible con C ++ 17.

Instalarlo usando pip:

    $ pip install cppyy

Para proyectos pequeños, simplemente cargue la biblioteca relevante y los encabezados que le interesen. Por ejemplo, tome el código del ejemplo ctypes de este hilo, pero divídalo en secciones de encabezado y código:

    $ cat foo.h
    class Foo {
    public:
        void bar();
    };

    $ cat foo.cpp
    #include "foo.h"
    #include <iostream>

    void Foo::bar() { std::cout << "Hello" << std::endl; }

Compilarlo:

    $ g++ -c -fPIC foo.cpp -o foo.o
    $ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so  foo.o

y úsalo:

    $ python
    >>> import cppyy
    >>> cppyy.include("foo.h")
    >>> cppyy.load_library("foo")
    >>> from cppyy.gbl import Foo
    >>> f = Foo()
    >>> f.bar()
    Hello
    >>>

Los proyectos grandes son compatibles con la carga automática de información de reflexión preparada y los fragmentos cmake para crearlos, de modo que los usuarios de los paquetes instalados puedan simplemente ejecutar:

    $ python
    >>> import cppyy
    >>> f = cppyy.gbl.Foo()
    >>> f.bar()
    Hello
    >>>

Gracias a LLVM, son posibles funciones avanzadas, como la creación de instancias de plantilla automática. Para continuar el ejemplo:

    >>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
    >>> v.push_back(f)
    >>> len(v)
    1
    >>> v[0].bar()
    Hello
    >>>

Nota: soy el autor de cppyy.


3
En realidad, no es así: Cython es un lenguaje de programación similar a Python para escribir módulos de extensión en C para Python (el código de Cython se traduce a C, junto con la plantilla de C-API necesaria). Proporciona algo de soporte básico de C ++. La programación con cppyy solo involucra Python y C ++, sin extensiones de lenguaje. Es totalmente en tiempo de ejecución y no genera código sin conexión (la generación diferida se escala mucho mejor). Está dirigido a C ++ moderno (incluidas instancias automáticas de plantillas, movimientos, initializer_lists, lambda's, etc., etc.) y PyPy es compatible de forma nativa (es decir, no a través de la capa lenta de emulación C-API).
Wim Lavrijsen

2
Este documento PyHPC'16 contiene una gama de números de referencia. Desde entonces, ha habido mejoras definitivas en el lado de CPython.
Wim Lavrijsen

Me gusta este enfoque, ya que no tiene que hacer el trabajo de integración adicional swig, ctypeso boost.python. En lugar de tener que escribir código para que Python funcione con su código C ++ ... Python hace el trabajo duro para descubrir C ++. Suponiendo que realmente funcione.
Trevor Boyd Smith

cppyy es muy interesante! Veo en los documentos que se maneja la redistribución y el empaquetado previo. ¿Se sabe que esto funciona bien con herramientas que también empaquetan código de Python (por ejemplo, PyInstaller)? ¿Y esto está relacionado con el proyecto ROOT o aprovecha su trabajo?
JimB

¡Gracias! No estoy familiarizado con PyInstaller, pero los "diccionarios" que empaquetan declaraciones, rutas y encabezados son códigos C ++ compilados en bibliotecas compartidas. Dado que cppyy se usa para enlazar código C ++, supongo que manejar algo más de código C ++ debería estar bien. Y ese código no depende de la Python C-API (solo lo es el módulo libcppyy), lo que simplifica las cosas. cppyy se puede instalar desde conda-forge o pypi (pip), por lo que cualquiera de esos entornos funciona, seguro. Sí, cppyy comenzó como una bifurcación de PyROOT, pero desde entonces ha mejorado tanto, que el equipo ROOT está reforzando PyROOT sobre cppyy.
Wim Lavrijsen


15

Nunca lo he usado, pero he escuchado cosas buenas sobre los ctypes . Si está intentando usarlo con C ++, asegúrese de evadir el cambio de nombre a través de extern "C". Gracias por el comentario, Florian Bösch.


13

Creo que cffi para python puede ser una opción.

El objetivo es llamar al código C desde Python. Debería poder hacerlo sin aprender un tercer idioma: cada alternativa requiere que aprenda su propio idioma (Cython, SWIG) o API (ctypes). Así que tratamos de asumir que conoces Python y C y minimizar los bits adicionales de API que necesitas aprender.

http://cffi.readthedocs.org/en/release-0.7/


2
Creo que esto solo puede llamar c (no c ++), aún +1 (realmente me gusta cffi).
Andy Hayden

8

La pregunta es cómo llamar a una función C desde Python, si entendí correctamente. Entonces la mejor apuesta son los Ctypes (por cierto, portátiles en todas las variantes de Python).

>>> from ctypes import *
>>> libc = cdll.msvcrt
>>> print libc.time(None)
1438069008
>>> printf = libc.printf
>>> printf("Hello, %s\n", "World!")
Hello, World!
14
>>> printf("%d bottles of beer\n", 42)
42 bottles of beer
19

Para obtener una guía detallada, puede consultar el artículo de mi blog .


Vale la pena señalar que, si bien los ctypes son portátiles, su código requiere una biblioteca C específica de Windows.
Palec


6

Cython es definitivamente el camino a seguir, a menos que anticipe escribir envoltorios Java, en cuyo caso SWIG puede ser preferible.

Recomiendo usar la runcythonutilidad de línea de comandos, hace que el proceso de usar Cython sea extremadamente fácil. Si necesita pasar datos estructurados a C ++, eche un vistazo a la biblioteca de protobuf de Google, es muy conveniente.

Aquí hay algunos ejemplos mínimos que hice que usa ambas herramientas:

https://github.com/nicodjimenez/python2cpp

Espero que pueda ser un punto de partida útil.


5

Primero debes decidir cuál es tu propósito particular. La documentación oficial de Python sobre la extensión e incrustación del intérprete de Python se mencionó anteriormente, puedo agregar una buena descripción general de las extensiones binarias . Los casos de uso se pueden dividir en 3 categorías:

  • módulos de acelerador : para ejecutarse más rápido que el código Python puro equivalente que se ejecuta en CPython.
  • módulos de envoltura : para exponer las interfaces C existentes al código Python.
  • acceso al sistema de bajo nivel : para acceder a las funciones de nivel inferior del tiempo de ejecución de CPython, el sistema operativo o el hardware subyacente.

Para dar una perspectiva más amplia a otros interesados ​​y dado que su pregunta inicial es un poco vaga ("a una biblioteca C o C ++"), creo que esta información puede ser interesante para usted. En el enlace de arriba puede leer las desventajas de usar extensiones binarias y sus alternativas.

Además de las otras respuestas sugeridas, si desea un módulo acelerador, puede probar Numba . Funciona "generando código de máquina optimizado utilizando la infraestructura del compilador LLVM en el momento de la importación, el tiempo de ejecución o de forma estática (utilizando la herramienta pycc incluida)".


3

Me encanta cppyy, hace que sea muy fácil extender Python con código C ++, aumentando drásticamente el rendimiento cuando sea necesario.

Es poderoso y francamente muy simple de usar,

Aquí es un ejemplo de cómo puede crear una matriz numpy y pasarla a una función miembro de la clase en C ++.

cppyy_test.py

import cppyy
import numpy as np
cppyy.include('Buffer.h')


s = cppyy.gbl.Buffer()
numpy_array = np.empty(32000, np.float64)
s.get_numpy_array(numpy_array.data, numpy_array.size)
print(numpy_array[:20])

Buffer.h

struct Buffer {
  void get_numpy_array(double *ad, int size) {
    for( long i=0; i < size; i++)
        ad[i]=i;
  }
};

También puede crear un módulo Python muy fácilmente (con CMake), de esta manera evitará recompilar el código C ++ todo el tiempo.


2

Ejemplo ejecutable mínimo de pybind11

pybind11 se mencionó anteriormente en https://stackoverflow.com/a/38542539/895245 pero me gustaría dar aquí un ejemplo de uso concreto y alguna discusión adicional sobre la implementación.

En general, recomiendo pybind11 porque es realmente fácil de usar: solo incluye un encabezado y luego pybind11 usa la magia de la plantilla para inspeccionar la clase de C ++ que desea exponer a Python y lo hace de forma transparente.

La desventaja de esta plantilla mágica es que ralentiza la compilación inmediatamente agregando unos segundos a cualquier archivo que use pybind11, vea por ejemplo la investigación realizada sobre este tema . PyTorch está de acuerdo .

Aquí hay un ejemplo ejecutable mínimo para darle una idea de lo increíble que es pybind11:

class_test.cpp

#include <string>

#include <pybind11/pybind11.h>

struct ClassTest {
    ClassTest(const std::string &name) : name(name) { }
    void setName(const std::string &name_) { name = name_; }
    const std::string &getName() const { return name; }
    std::string name;
};

namespace py = pybind11;

PYBIND11_PLUGIN(class_test) {
    py::module m("my_module", "pybind11 example plugin");
    py::class_<ClassTest>(m, "ClassTest")
        .def(py::init<const std::string &>())
        .def("setName", &ClassTest::setName)
        .def("getName", &ClassTest::getName)
        .def_readwrite("name", &ClassTest::name);
    return m.ptr();
}

class_test_main.py

#!/usr/bin/env python3

import class_test

my_class_test = class_test.ClassTest("abc");
print(my_class_test.getName())
my_class_test.setName("012")
print(my_class_test.getName())
assert(my_class_test.getName() == my_class_test.name)

Compilar y ejecutar:

#!/usr/bin/env bash
set -eux
g++ `python3-config --cflags` -shared -std=c++11 -fPIC class_test.cpp \
  -o class_test`python3-config --extension-suffix` `python3-config --libs`
./class_test_main.py

¡Este ejemplo muestra cómo pybind11 le permite exponer sin esfuerzo la ClassTestclase C ++ a Python! La compilación produce un archivo llamado class_test.cpython-36m-x86_64-linux-gnu.soque class_test_main.pyse selecciona automáticamente como el punto de definición paraclass_test módulo definido de forma nativa.

Tal vez la comprensión de lo increíble que esto es solo se hunde si intenta hacer lo mismo a mano con la API nativa de Python, vea, por ejemplo, este ejemplo de hacer eso, que tiene aproximadamente 10 veces más código: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c En ese ejemplo, puede ver cómo el código C debe definir dolorosa y explícitamente la clase de Python poco a poco con toda la información que contiene (miembros, métodos, más metadatos ...). Ver también:

pybind11 afirma ser similar al Boost.Pythonmencionado en https://stackoverflow.com/a/145436/895245 pero más mínimo porque se libera de la hinchazón de estar dentro del proyecto Boost:

pybind11 es una biblioteca ligera de solo encabezado que expone los tipos C ++ en Python y viceversa, principalmente para crear enlaces Python del código C ++ existente. Sus objetivos y sintaxis son similares a la excelente biblioteca Boost.Python de David Abrahams: para minimizar el código repetitivo en los módulos de extensión tradicionales al inferir información de tipo mediante la introspección en tiempo de compilación.

El problema principal con Boost.Python, y la razón para crear un proyecto tan similar, es Boost. Boost es un conjunto de bibliotecas de utilidades enormemente grande y complejo que funciona con casi todos los compiladores de C ++ existentes. Esta compatibilidad tiene su costo: son necesarios trucos de plantilla arcanos y soluciones alternativas para admitir las muestras de compilador más antiguas y con errores. Ahora que los compiladores compatibles con C ++ 11 están ampliamente disponibles, esta maquinaria pesada se ha convertido en una dependencia excesivamente grande e innecesaria.

Piense en esta biblioteca como una pequeña versión autónoma de Boost.Python con todo despojado que no es relevante para la generación de enlaces. Sin comentarios, los archivos de encabezado principales solo requieren ~ 4K líneas de código y dependen de Python (2.7 o 3.xo PyPy2.7> = 5.7) y la biblioteca estándar de C ++. Esta implementación compacta fue posible gracias a algunas de las nuevas características del lenguaje C ++ 11 (específicamente: tuplas, funciones lambda y plantillas variadas). Desde su creación, esta biblioteca ha crecido más allá de Boost.Python de muchas maneras, lo que lleva a un código de enlace dramáticamente más simple en muchas situaciones comunes.

pybind11 también es la única alternativa no nativa destacada por la documentación de enlace actual de Microsoft Python C en: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( archivo ).

Probado en Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.