Vous appelez C / C ++ à partir de Python?


521

Quelle serait la manière la plus rapide de construire une liaison Python à une bibliothèque C ou C ++?

(J'utilise Windows si cela est important.)

Réponses:


170

Vous devriez jeter un œil à Boost.Python . Voici la courte introduction tirée de leur site Web:

La bibliothèque Boost Python est un cadre pour interfacer Python et C ++. Il vous permet d'exposer rapidement et de manière transparente des fonctions et des objets de classes C ++ à Python, et vice-versa, sans utiliser d'outils spéciaux - juste votre compilateur C ++. Il est conçu pour encapsuler les interfaces C ++ de manière non intrusive, de sorte que vous ne devriez pas avoir à modifier du tout le code C ++ afin de l'encapsuler, 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 sa syntaxe pour les utilisateurs, de sorte que le code d'habillage prend l'apparence d'une sorte de langage de définition d'interface déclarative (IDL).


Boost.Python est l'une des bibliothèques les plus conviviales de Boost, pour une API d'appel de fonction simple, elle est assez simple et fournit un passe-partout que vous auriez à écrire vous-même. C'est un peu plus compliqué si vous voulez exposer une API orientée objet.
jwfearn le

15
Boost.Python est la pire chose imaginable. Pour chaque nouvelle machine et à chaque mise à niveau, cela entraîne des problèmes de liaison.
miller

15
Près de 11 ans plus tard, le temps d'une réflexion sur la qualité de cette réponse?
J Evans

4
Est-ce toujours la meilleure approche pour interfacer python et c ++?
tushaR

8
Vous pouvez peut-être essayer pybind11 qui est léger par rapport à boost.
jdhao

659

Le module ctypes fait partie de la bibliothèque standard et est donc plus stable et largement disponible que swig , ce qui a toujours eu tendance à me poser des problèmes .

Avec les ctypes, vous devez satisfaire toute dépendance de temps de compilation sur python, et votre liaison fonctionnera sur n'importe quel python qui a des ctypes, pas seulement celui avec lequel il a été compilé.

Supposons que vous ayez un exemple de classe C ++ simple dans lequel vous souhaitez parler dans un fichier appelé foo.cpp:

#include <iostream>

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

Comme les ctypes ne peuvent parler qu'aux fonctions C, vous devez fournir ceux qui les déclarent comme "C" externe

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

Ensuite, vous devez compiler cela 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 l'avez, vous pouvez l'appeler comme

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

14
C'est à peu près ce que boost.python fait pour vous en un seul appel de fonction.
Martin Beckett

203
ctypes est dans la bibliothèque standard de python, swig et boost ne le sont pas. Swig et boost reposent sur des modules d'extension et sont donc liés à des versions mineures de python, ce que ne sont pas les objets partagés indépendants. la construction d'une swig ou d'un boost boosters peut être pénible, ctypes ne nécessite aucune construction.
Florian Bösch le

25
boost repose sur la magie du modèle vaudou et un système de construction entièrement personnalisé, ctypes repose sur la simplicité. ctypes est dynamique, boost est statique. ctypes peut gérer différentes versions de bibliothèques. boost ne peut pas.
Florian Bösch le

32
Sous Windows, j'ai dû spécifier __declspec (dllexport) dans mes signatures de fonction pour que Python puisse les voir. D'après l'exemple ci-dessus, cela correspondrait à: extern "C" { __declspec(dllexport) Foo* Foo_new(){ return new Foo(); } __declspec(dllexport) void Foo_bar(Foo* foo){ foo->bar(); } }
Alan Macdonald

13
N'oubliez pas de supprimer le pointeur par la suite en fournissant par exemple une Foo_deletefonction et en l'appelant depuis un destructeur python ou en enveloppant l'objet dans une ressource .
Adversus

58

La façon la plus rapide de le faire est d'utiliser SWIG .

Exemple du didacticiel SWIG :

/* 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. Dans certains systèmes, les fichiers d'en-tête python seront également dans /usr/include/python2.7 en fonction de la façon dont vous l'avez installé.

Du tutoriel:

SWIG est un compilateur C ++ assez complet avec prise en charge de presque toutes les fonctionnalités de 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 empaqueter des structures et des classes dans des classes proxy dans le langage cible - exposant la fonctionnalité sous-jacente de manière très naturelle.


50

J'ai commencé mon voyage 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) :-)

Ayant essayé les solutions basées à la fois sur ctypes et boost.python (et n'étant pas ingénieur logiciel), je les ai trouvées complexes lorsqu'une 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 mettre à la disposition de Python une fonction C ++ qui prend une matrice sous la forme d'un vecteur STL 2D et renvoie une moyenne de chaque ligne (en tant que vecteur STL 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

Nous compilons enfin le fichier source de l'interface C ++ généré et lions tout ensemble pour générer une bibliothèque partagée directement importable par Python (le "_" compte):

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

Une implémentation de cas réel où dans le code C ++ les vecteurs stl sont passés en tant que références non const et donc disponibles par python en tant que paramètres de sortie: lobianco.org/antonello/personal:portfolio:portopt
Antonello

33

Il y a aussi pybind11, qui est comme une version allégée de Boost.Python et compatible avec tous les compilateurs C ++ modernes:

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


1
Aujourd'hui !! 2020 Cela devrait être la meilleure réponse! Il s'agit d'une bibliothèque uniquement d'en-tête de modèle. De nombreux projets importants et pertinents le recommandent, comme Pytorch pytorch.org/tutorials/advanced/cpp_extension.html Fonctionne également entièrement sur VS CommunityWindows
eusoubrasileiro

30

Check-out pyrex ou Cython . Ce sont des langages de type Python pour l'interfaçage entre C / C ++ et Python.


1
+1 pour Cython! Je n'ai pas essayé cffi donc je ne peux pas dire ce qui est mieux, mais j'ai eu de très bonnes expériences avec Cython - vous écrivez toujours du code Python mais vous pouvez utiliser C dedans. J'ai eu du mal à configurer le processus de construction avec Cython, que j'ai expliqué plus tard dans un article de blog: martinsosic.com/development/2016/02/08/…
Martinsos

Vous souhaiterez peut-être améliorer la réponse pour ne plus être une réponse de lien uniquement.
Adelin

J'utilise Cython depuis environ une semaine et je l'aime beaucoup: 1) J'ai vu des ctypes en cours d'utilisation et il est laid et très sujet aux erreurs avec de nombreux pièges 2) Il vous permet de prendre du code Python et d'accélérer de la saisie statique de choses seules 3) Il est simple d'écrire des wrappers Python pour les méthodes et les objets C / C ++ 4) Il est toujours bien supporté. Cela pourrait faire plus de conseils concernant l'installation sur venvs et la compilation croisée, ce qui a pris un peu de temps à travailler. Il y a un très bon tutoriel vidéo de 4 heures ici: youtube.com/watch?v=gMvkiQ-gOW8
Den-Jason

22

Pour le C ++ moderne, utilisez cppyy: http://cppyy.readthedocs.io/en/latest/

Il est basé sur Cling, l'interpréteur C ++ pour Clang / LLVM. Les liaisons sont au moment de l'exécution et aucun langage intermédiaire supplémentaire n'est nécessaire. Grâce à Clang, il prend en charge 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. Par exemple, prenez 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 les 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 des 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
    >>>

Remarque: je suis l'auteur de cppyy.


3
Ce n'est pas vraiment le cas: Cython est un langage de programmation de type Python pour écrire des modules d'extension C pour Python (le code Cython est traduit en C, avec le passe-partout C-API nécessaire). Il fournit un support de base C ++. La programmation avec cppyy implique uniquement Python et C ++, pas d'extensions de langage. Il est entièrement exécuté et ne génère pas de code hors ligne (la génération paresseuse évolue beaucoup mieux). Il cible le C ++ moderne (y compris les instanciations de modèles automatiques, les mouvements, les listes d'initialisation, les lambda, etc., etc.) et PyPy est pris en charge de manière native (c'est-à-dire pas via la couche d'émulation C-API lente).
Wim Lavrijsen

2
Ce document PyHPC'16 contient une gamme de numéros de référence. Depuis lors, il y a eu des améliorations définitives du côté CPython.
Wim Lavrijsen

J'aime cette approche parce que vous n'avez pas à faire un travail d'intégration supplémentaire swig, ctypesou boost.python. Au lieu d'avoir à écrire du code pour que python fonctionne avec votre code c ++ ... python fait le travail difficile pour comprendre c ++. En supposant que cela fonctionne réellement.
Trevor Boyd Smith du

cppyy est très intéressant! Je vois dans les documents que la redistribution et le pré-emballage sont gérés. Est-ce que cela fonctionne bien avec des outils qui empaquettent du code python (par exemple, PyInstaller)? Et est-ce lié au projet ROOT, ou tirer parti de son travail?
JimB

Merci! Je ne connais pas PyInstaller, mais les "dictionnaires" qui regroupent les déclarations, les chemins et les en-têtes avancés sont des codes C ++ compilés dans des bibliothèques partagées. Étant donné que cppyy est utilisé pour lier le code C ++, je suppose que la gestion d'un peu plus de code C ++ devrait être correcte. Et ce code ne dépend pas de l'API C Python (seul le module libcppyy l'est), ce qui simplifie les choses. cppyy lui-même peut être installé à partir de conda-forge ou de pypi (pip), donc n'importe lequel de ces environnements fonctionne, c'est sûr. Oui, cppyy a commencé comme une fourche de PyROOT, mais il s'est tellement amélioré depuis, que l'équipe ROOT rebase PyROOT sur cppyy.
Wim Lavrijsen


15

Je ne l'ai jamais utilisé mais j'ai entendu de bonnes choses sur les types de c . Si vous essayez de l'utiliser avec C ++, assurez-vous d'éviter le changement de nom via extern "C". Merci pour le commentaire, Florian Bösch.


13

Je pense que cffi pour python peut être une option.

Le but est d'appeler du code C depuis Python. Vous devriez pouvoir le faire sans apprendre une troisième langue: chaque alternative vous oblige à apprendre leur propre langage (Cython, SWIG) ou API (ctypes). Nous avons donc essayé de supposer que vous connaissez Python et C et de minimiser les bits supplémentaires d'API que vous devez apprendre.

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


2
Je pense que cela ne peut appeler que c (pas c ++), toujours +1 (j'aime vraiment cffi).
Andy Hayden

8

La question est de savoir comment appeler une fonction C à partir de Python, si j'ai bien compris. Ensuite, 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 .


Il peut être intéressant de noter que même si les types de c sont portables, votre code nécessite une bibliothèque C spécifique à Windows.
Palec


6

Cython est certainement la voie à suivre, sauf si vous prévoyez d'écrire des wrappers Java, auquel cas SWIG peut être préférable.

Je recommande d'utiliser l' runcythonutilitaire de ligne de commande, cela rend le processus d'utilisation de Cython extrêmement simple. Si vous devez transmettre des données structurées à C ++, jetez un œil à la bibliothèque de protobuf de Google, c'est très pratique.

Voici quelques exemples minimes que j'ai faits qui utilisent les deux outils:

https://github.com/nicodjimenez/python2cpp

J'espère que cela peut être un point de départ utile.


5

Vous devez d'abord décider quel est votre objectif particulier. La documentation officielle de Python sur l' extension et l'incorporation de l'interpréteur 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:

  • modules accélérateurs : pour s'exécuter plus rapidement que le code Python pur équivalent s'exécute dans CPython.
  • modules wrapper : pour exposer les interfaces C existantes au code Python.
  • accès au système de bas niveau : pour accéder aux fonctionnalités de niveau inférieur du runtime CPython, du système d'exploitation ou du matériel sous-jacent.

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 les inconvénients de l'utilisation des extensions binaires et de ses alternatives.

Outre les autres réponses suggérées, si vous voulez un module accélérateur, vous pouvez essayer Numba . Il fonctionne "en générant du code machine optimisé à l'aide de l'infrastructure du compilateur LLVM au moment de l'importation, de l'exécution ou de façon statique (à l'aide de l'outil pycc inclus)".


3

J'adore cppyy, il est très facile d'étendre Python avec du code C ++, augmentant considérablement les performances en cas de besoin.

Il est puissant et franchement très simple à utiliser,

ici, c'est un exemple de la façon dont vous pouvez créer un tableau numpy et le passer à une fonction membre de classe 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;
  }
};

Vous pouvez également créer un module Python très facilement (avec CMake), de cette façon vous éviterez de recompiler le code C ++ tout le temps.


2

exemple exécutable minimal pybind11

pybind11 a déjà été mentionné sur https://stackoverflow.com/a/38542539/895245 mais je voudrais donner ici un exemple d'utilisation concret et quelques discussions supplémentaires sur la mise en œuvre.

Dans l'ensemble, je recommande fortement pybind11 car il est vraiment facile à utiliser: vous incluez simplement un en-tête, puis pybind11 utilise la magie des modèles pour inspecter la classe C ++ que vous souhaitez exposer à Python et le fait de manière transparente.

L'inconvénient de ce modèle magique est qu'il ralentit la compilation en ajoutant immédiatement quelques secondes à tout fichier utilisant pybind11, voir par exemple l'enquête effectuée sur ce problème . PyTorch est d'accord .

Voici un exemple exécutable minimal pour vous donner une idée de la qualité de 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)

Compiler et exécuter:

#!/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

Cet exemple montre comment pybind11 vous permet d'exposer sans effort la ClassTestclasse C ++ à Python! La compilation produit un fichier nommé class_test.cpython-36m-x86_64-linux-gnu.soqui class_test_main.pyprend automatiquement le point de définition duclass_test module défini nativement.

Peut-être que la réalisation de la façon dont c'est génial ne s'enfonce que si vous essayez de faire la même chose à la main avec l'API Python native, voir par exemple cet exemple de faire cela, qui a environ 10 fois plus de code: https://github.com /cirosantilli/python-cheat/blob/4f676f62e87810582ad53b2fb426b74eae52aad5/py_from_c/pure.c Sur cet exemple, vous pouvez voir comment le code C doit définir douloureusement et explicitement la classe Python bit par bit avec toutes les informations qu'il contient (membres, méthodes, plus loin) métadonnées ...). Voir également:

pybind11 prétend être similaire à Boost.Pythonce qui a été mentionné sur https://stackoverflow.com/a/145436/895245 mais plus minimal car il est libéré du ballonnement d'être à l'intérieur du projet Boost:

pybind11 est une bibliothèque légère uniquement en-tête qui expose les types C ++ en Python et vice versa, principalement pour créer des liaisons Python de code C ++ existant. Ses objectifs et sa syntaxe sont similaires à l'excellente bibliothèque Boost.Python de David Abrahams: minimiser le code passe-partout dans les modules d'extension traditionnels en déduisant les informations de type à l'aide d'une introspection au moment de la compilation.

Le principal problème avec Boost.Python - et la raison de la création d'un projet similaire - est Boost. Boost est une suite de bibliothèques d'utilitaires extrêmement vaste et complexe qui fonctionne avec presque tous les compilateurs C ++ existants. Cette compatibilité a un coût: des trucs et des solutions de rechange arcaniques sont nécessaires pour prendre en charge les spécimens de compilateur les plus anciens et les plus bogues. Maintenant que les compilateurs compatibles C ++ 11 sont largement disponibles, cette machinerie lourde est devenue une dépendance excessivement grande et inutile.

Considérez cette bibliothèque comme une petite version autonome de Boost.Python avec tout ce qui n'est pas pertinent pour la génération de liaisons. Sans commentaires, les fichiers d'en-tête de base ne nécessitent que ~ 4K lignes de code et dépendent de Python (2.7 ou 3.x, ou PyPy2.7> = 5.7) et de la bibliothèque standard C ++. Cette implémentation compacte a été possible grâce à certaines des nouvelles fonctionnalités du langage C ++ 11 (en particulier: tuples, fonctions lambda et modèles variadic). Depuis sa création, cette bibliothèque s'est développée au-delà de Boost.Python à bien des égards, conduisant à un code de liaison considérablement plus simple dans de nombreuses situations courantes.

pybind11 est également la seule alternative non native mise en évidence par la documentation de liaison Microsoft Python C actuelle sur: https://docs.microsoft.com/en-us/visualstudio/python/working-with-c-cpp-python-in- visual-studio? view = vs-2019 ( archive ).

Testé sur Ubuntu 18.04, pybind11 2.0.1, Python 3.6.8, GCC 7.4.0.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.