Respuestas:
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).
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
extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
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.
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
También existe pybind11
, que es como una versión ligera de Boost.Python y compatible con todos los compiladores de C ++ modernos:
Pytorch
pytorch.org/tutorials/advanced/cpp_extension.html También funciona completamente en VS Community
Windows
Echa un vistazo a pyrex o Cython . Son lenguajes similares a Python para la interfaz entre C / C ++ y Python.
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.
swig
, ctypes
o 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.
Este documento, que afirma que Python es todo lo que un científico necesita , básicamente dice: Primero prototipo de todo en Python. Luego, cuando necesite acelerar una parte, use SWIG y traduzca esta parte a C.
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.
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.
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 .
Uno de los documentos oficiales de Python contiene detalles sobre cómo extender Python usando C / C ++ . Incluso sin el uso de SWIG , es bastante sencillo y funciona perfectamente bien en Windows.
Cython es definitivamente el camino a seguir, a menos que anticipe escribir envoltorios Java, en cuyo caso SWIG puede ser preferible.
Recomiendo usar la runcython
utilidad 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.
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:
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)".
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.
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 ClassTest
clase C ++ a Python! La compilación produce un archivo llamado class_test.cpython-36m-x86_64-linux-gnu.so
que class_test_main.py
se 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.Python
mencionado 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.