Quel serait le moyen le plus rapide de construire une liaison Python à une bibliothèque C ou C++?
(J'utilise Windows si cela compte.)
Vous devriez jeter un oeil à Boost.Python . Voici la courte introduction tirée de leur site web:
La bibliothèque Boost Python est un cadre pour l’interfaçage de Python et de C++. Il vous permet d’exposer rapidement et de manière transparente des fonctions et des objets de classes C++ à Python, et inversement, sans utiliser d’outils spéciaux, mais uniquement votre compilateur C++. Il est conçu pour envelopper les interfaces C++ de manière non intrusive, de sorte que vous ne devriez pas avoir à changer le code C++ afin de le recouvrir, ce qui rend Boost.Python idéal pour exposer des bibliothèques tierces à Python. L'utilisation par la bibliothèque de techniques avancées de métaprogrammation simplifie la syntaxe pour les utilisateurs, de sorte que le code d'habillage prend l'aspect d'une sorte de langage de définition d'interface déclaratif (IDL).
ctypes fait partie de la bibliothèque standard, et est donc plus stable et largement disponible que swig , qui a toujours tendance à me donner problèmes .
Avec ctypes, vous devez satisfaire toutes les dépendances de python lors de la compilation, et votre liaison fonctionnera sur tous les python ayant des ctypes, et pas seulement ceux sur lesquels ils ont été compilés.
Supposons que vous souhaitiez parler d'un exemple de classe simple C++ dans un fichier nommé foo.cpp:
#include <iostream>
class Foo{
public:
void bar(){
std::cout << "Hello" << std::endl;
}
};
Puisque les types ne peuvent parler qu’aux fonctions C, vous devez fournir celles qui les déclarent en tant qu’extern "C".
extern "C" {
Foo* Foo_new(){ return new Foo(); }
void Foo_bar(Foo* foo){ foo->bar(); }
}
Ensuite, vous devez le compiler dans une bibliothèque partagée
g++ -c -fPIC foo.cpp -o foo.o
g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
Et enfin, vous devez écrire votre wrapper python (par exemple dans 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)
Une fois que vous avez cela, vous pouvez l'appeler comme
f = Foo()
f.bar() #and you will see "Hello" on the screen
Le moyen le plus rapide de procéder consiste à utiliser SWIG .
Exemple de SWIG tutorial :
/* File : example.c */
int fact(int n) {
if (n <= 1) return 1;
else return n*fact(n-1);
}
Fichier d'interface:
/* example.i */
%module example
%{
/* Put header files here or function declarations like below */
extern int fact(int n);
%}
extern int fact(int n);
Construire un module Python sous 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
Usage:
>>> import example
>>> example.fact(5)
120
Notez que vous devez avoir python-dev. De plus, dans certains systèmes, les fichiers d'en-tête python seront dans /usr/include/python2.7 en fonction de la manière dont vous l'avez installé.
Du tutoriel:
SWIG est un compilateur C++ assez complet prenant en charge presque toutes les fonctionnalités du langage. Cela inclut le prétraitement, les pointeurs, les classes, l'héritage et même les modèles C++. SWIG peut également être utilisé pour conditionner les structures et les classes en classes proxy dans le langage cible - en exposant les fonctionnalités sous-jacentes de manière très naturelle.
J'ai commencé mon parcours dans la liaison Python <-> C++ à partir de cette page, dans le but de lier des types de données de haut niveau (vecteurs STL multidimensionnels avec des listes Python) :-)
Après avoir essayé les solutions basées à la fois sur ctypes et boost.python (et n'étant pas un ingénieur en logiciel), je les ai trouvées complexes lorsque la liaison de types de données de haut niveau est requise, alors que j'ai trouvé SWIG beaucoup plus simple pour de tels cas.
Cet exemple utilise donc SWIG, et il a été testé sous Linux (mais SWIG est disponible et est également largement utilisé sous Windows).
L'objectif est de rendre disponible une fonction C++ à Python qui prend une matrice sous la forme d'un vecteur STL 2D et renvoie une moyenne de chaque ligne (sous forme d'un vecteur STD 1D).
Le code en C++ ("code.cpp") est le suivant:
#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;
}
L'en-tête équivalent ("code.h") est:
#ifndef _code
#define _code
#include <vector>
std::vector<double> average (std::vector< std::vector<double> > i_matrix);
#endif
Nous compilons d’abord le code C++ pour créer un fichier objet:
g++ -c -fPIC code.cpp
Nous définissons ensuite un fichier de définition d'interface SWIG ("code.i") pour nos fonctions 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"
À l'aide de SWIG, nous générons un code source d'interface C++ à partir du fichier de définition d'interface SWIG.
swig -c++ -python code.i
Enfin, nous compilons le fichier source de l’interface C++ généré et lions le tout pour créer une bibliothèque partagée directement importable par Python (le "_" est important):
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
Nous pouvons maintenant utiliser la fonction dans les scripts 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
Il y a aussi pybind11
, qui est comme une version allégée de Boost.Python et compatible avec tous les compilateurs C++ modernes:
Cet article, prétendant que Python est tout ce dont un scientifique a besoin , dit essentiellement: Premier prototype, tout en Python. Ensuite, lorsque vous devez accélérer une partie, utilisez SWIG et traduisez cette partie en C.
Je ne l’ai jamais utilisé mais j’ai entendu de bonnes choses à propos de ctypes . Si vous essayez de l’utiliser avec C++, veillez à éviter les manipulations de nom via extern "C"
. Merci pour le commentaire, Florian Bösch.
Pour le C++ moderne, utilisez cppyy: http://cppyy.readthedocs.io/en/latest/
Il est basé sur Cling, l'interpréteur C++ de Clang/LLVM. Les liaisons se font au moment de l'exécution et aucun langage intermédiaire supplémentaire n'est nécessaire. Grâce à Clang, il supporte le C++ 17.
Installez-le en utilisant pip:
$ pip install cppyy
Pour les petits projets, chargez simplement la bibliothèque appropriée et les en-têtes qui vous intéressent. prendre le code de l'exemple ctypes est ce fil, mais divisé en sections d'en-tête et de code:
$ cat foo.h
class Foo {
public:
void bar();
};
$ cat foo.cpp
#include "foo.h"
#include <iostream>
void Foo::bar() { std::cout << "Hello" << std::endl; }
Compilez-le:
$ g++ -c -fPIC foo.cpp -o foo.o
$ g++ -shared -Wl,-soname,libfoo.so -o libfoo.so foo.o
et l'utiliser:
$ python
>>> import cppyy
>>> cppyy.include("foo.h")
>>> cppyy.load_library("foo")
>>> from cppyy.gbl import Foo
>>> f = Foo()
>>> f.bar()
Hello
>>>
Les grands projets sont pris en charge avec le chargement automatique des informations de réflexion préparées et des fragments cmake pour les créer, afin que les utilisateurs des packages installés puissent simplement exécuter:
$ python
>>> import cppyy
>>> f = cppyy.gbl.Foo()
>>> f.bar()
Hello
>>>
Grâce à LLVM, des fonctionnalités avancées sont possibles, telles que l’instanciation automatique de modèles. Pour continuer l'exemple:
>>> v = cppyy.gbl.std.vector[cppyy.gbl.Foo]()
>>> v.Push_back(f)
>>> len(v)
1
>>> v[0].bar()
Hello
>>>
Note: je suis l'auteur de cppyy.
Je pense que cffi pour python peut être une option.
L'objectif est d'appeler le code C à partir de Python. Vous devriez pouvoir le faire sans apprendre une troisième langue: chaque alternative nécessite que vous appreniez leur propre langue (Cython, SWIG) ou API (ctypes). Nous avons donc essayé de supposer que vous connaissez Python et C, et que vous minimisiez les bits d’API supplémentaires que vous devez apprendre.
La question est de savoir comment appeler une fonction C depuis Python, si j'ai bien compris. Alors le meilleur pari est Ctypes (BTW portable sur toutes les 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
Pour un guide détaillé, vous pouvez vous référer à mon article de blog .
Un des documents officiels Python contient des détails sur étendre Python à l'aide de C/C++ . Même sans l'utilisation de SWIG , il est assez simple et fonctionne parfaitement sous Windows.
Tout d'abord, vous devez décider quel est votre but particulier. La documentation officielle Python sur étendre et incorporer l'interprète Python a été mentionnée ci-dessus, je peux ajouter un bon aperçu des extensions binaires . Les cas d'utilisation peuvent être divisés en 3 catégories:
Afin de donner une perspective plus large aux autres personnes intéressées et puisque votre question initiale est un peu vague ("à une bibliothèque C ou C++"), je pense que cette information pourrait vous intéresser. Sur le lien ci-dessus, vous pouvez lire sur les inconvénients de l'utilisation d'extensions binaires et de ses alternatives.
Outre les autres réponses suggérées, si vous voulez un module d'accélérateur, vous pouvez essayer Numba . Cela fonctionne "en générant un code machine optimisé à l'aide de l'infrastructure du compilateur LLVM au moment de l'importation, de l'exécution ou de manière statique (à l'aide de l'outil pycc inclus)".
Cython est définitivement la voie à suivre, à moins que vous ne prévoyiez écrire Java wrappers, auquel cas SWIG peut être préférable.
Je recommande d'utiliser l'utilitaire de ligne de commande runcython
, qui simplifie énormément l'utilisation de Cython. Si vous devez passer des données structurées au C++, jetez un coup d'œil à la bibliothèque protobuf de Google, c'est très pratique.
Voici un exemple minimal que j'ai fait qui utilise les deux outils:
https://github.com/nicodjimenez/python2cpp
J'espère que cela peut être un point de départ utile.
Vous pouvez utiliser Scapix Language Bridge pour générer automatiquement des liaisons Python directement à partir d'en-têtes C++ dans le cadre de la construction.
Ajoutez un appel à scapix_bridge_headers()
à votre fichier CMakeLists.txt, puis générez votre projet avec cmake -DSCAPIX_BRIDGE=python
. Voir complet exemple .
Disclaimer: Je suis l'auteur de Scapix Language Bridge .