Un serveur métier en Python / C++ 
Meet-up C++ 
02/10/2014 
Pierre Marquis & Alexandre Bonnasseau
Plan 
• Retour d’expérience 
• En pratique, quelques lignes de code
L'équipe dev carto 
Info 
trafic 
Itinéraire 
Plan 
Moteur de 
recherche 
géolocalisée 
Moteur de 
suggestion 
Géolocalisation 
Itinéraire 
transports 
en commun
Python WTF ??!
Python WTF ??! 
- Simplicité de code 
- Framework web léger 
- Richesse des modules 
- Facilité d'industrialisation
Notre philosophie 
- Python quand on peut 
- C++ quand c’est nécessaire
Chemin de l’équipe 
● Fonctions plus atomiques 
● Test U 
● Lint 
● Dojo 
● Pair programming 
● Code review
Schéma d’archi du plan
Chaîne d’industrialisation
Aujourd’hui 
+ 80% des serveurs migrés 
+ Langage très intéressant 
+ Framework mieux 
maîtrisé 
+ Dev plus rapide 
+ Plus d’agilité 
- Moins de maîtrise C++ 
- Multithread 
- Débogage binding
En pratique : 
appeler du C++ depuis Python
Cadre 
– Existence d’une base de code C++ fiable et testée 
– On souhaite prototyper rapidement (besoin mouvant) 
– Besoin d’exécution rapide pour des fonctions critiques
L'existant 
#define _USE_MATH_DEFINES 
#include <cmath> 
#include "geo.hpp" 
#include <sstream> 
#include <stdexcept> 
string geocoord2string(double angle) { 
if ( (angle > 180) || (angle < -180) ) 
throw invalid_argument("Invalid argument : angle must be beetween -180° and 180°"); 
int deg = int(floor(angle)); 
double rest = angle - deg; 
int minute = int(floor( rest * 60)); 
rest = rest * 60 - minute; 
int second = int(floor( rest * 60)); 
ostringstream result; 
result << deg << "° " << minute << "' " << second <<"''"; 
return result.str(); 
} 
double deg2rad(double deg) { 
return (deg * M_PI / 180); 
} 
GeoPoint::GeoPoint(double lat, double lng) : lat(lat), lng(lng) {} 
double GeoPoint::distance(const GeoPoint &other) { 
double nDLat = deg2rad(other.lat - this->lat); 
double nDLon = deg2rad(other.lng - this->lng); 
double thisLatRad = deg2rad(this->lat); 
double otherLatRad = deg2rad(other.lat); 
double nA = pow ( sin(nDLat/2), 2 ) + cos(thisLatRad) * cos(otherLatRad) * pow ( sin(nDLon/2), 2 ); 
double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA )); 
double nD = EARTH_RADIUS * nC; 
return nD; // Return our calculated distance 
}
Objectif 
Utiliser les fonctions C++ en python : 
$ python 
Python 2.7.3 
>>> import geo 
>>> print geo.geocoord2string(5.36) 
5° 21’ 36‘’ 
Projet complet disponible sur github : 
https://github.com/Mappy/un-serveur-metier-en-python-cpp
Un module python écrit en C++ 
D'après : https://docs.python.org/2/extending/extending.html 
geomodule.cpp 
#include "Python.h" 
#include "geo.hpp" 
static PyObject * geocoord2string_py(PyObject *self, PyObject *args) 
{ 
double angle = 0; 
if (!PyArg_ParseTuple(args, "d", &angle)) 
return NULL; 
string res = geocoord2string(angle); 
return Py_BuildValue("s", res.c_str()); 
} 
// La liste des fonctions qu'on expose en Python 
static PyMethodDef geoMethods[] = { 
{ "geocoord2string", geocoord2string_py, METH_VARARGS, 
"Convert a latitude or a longitude as an angle in a string in the form : d° m' s''." }, 
{ NULL, NULL, 0, NULL } /* Sentinel */ 
}; 
// La fonction initnomdumodule est appelée par l'interpréteur Python 
// à l'import du module 
PyMODINIT_FUNC initgeo(void) 
{ 
(void) Py_InitModule("geo", geoMethods); 
}
Avec Boost.Python ? 
geomudule.cpp 
#include <boost/python.hpp> 
#include <boost/python/module.hpp> 
#include "geo.hpp" 
using namespace boost::python; 
BOOST_PYTHON_MODULE(geo) 
{ 
def("geocoord2string", geocoord2string); 
class_<GeoPoint>("GeoPoint", init<double, double>()) 
.def("distance", &GeoPoint::distance) 
… C’est tout ! 
; 
}
Avec Boost.Python, on peut 
– Exposer des fonctions C++ en Python 
– Exposer des classes C++ en Python 
– Choisir quelles méthodes exposer pour une classe 
– Utiliser Boost::optional pour gérer les paramètres 
optionnels
Packager une extension Python 
Le fichier setup.py s’occupe de la compilation : 
from distutils.core import setup, Extension 
geo_module = Extension('geo', 
include_dirs = ['/usr/local/include',], 
library_dirs = ['/usr/local/lib',], 
extra_compile_args=['-std=c++11'], 
sources = ['geomodule.cpp', 'geo.cpp']) 
setup (name = 'Geo', 
version = '1.0', 
description = 'A geo package from Mappy', 
author = 'LBS team', 
author_email = 'lbs@mappy.com', 
url = 'http://github.com/mappy', 
long_description = ‘A demo package with geo functions', 
ext_modules = [geo_module]) 
Installer avec : 
python setup.py install 
Ou packager : 
python setup.py bdist
Focus : Gestion de la mémoire 
– Un code C++ qui conserve une référence sur 
un objet Python doit appeler la macro 
Py_INCREF() pour s'assurer que l'objet ne 
sera pas détruit 
– Il faut appeler Py_DECREF() pour libérer 
l'objet
Focus : Gestion des erreurs 
Le code suivant lève une exception C++ : 
import geo 
print geo.geocoord2string(181) 
Sans Boost.Python : 
terminate called after throwing an instance of 'std::invalid_argument' 
what(): Invalid argument : angle must be beetween -180° and 180° 
Avec Boost.Python : 
Traceback (most recent call last): 
File "<stdin>", line 1, in <module> 
ValueError: Invalid argument : angle must be beetween -180° and 180° 
Boost.Python permet également d'affiner la gestion des erreurs en associant des 
erreurs spécifiques Python aux exceptions C++
Un serveur de distance géographiques 
geoserveur.py 
from bottle import route, run, template 
from geo import GeoPoint 
@route('/distance/<lat1:float>,<lng1:float>/<lat2:float>,<lng2:float>') 
def index(lat1, lng1, lat2, lng2): 
dep = GeoPoint(lat1, lng1) 
arr = GeoPoint(lat2, lng2) 
return template("Distance = {{distance}}", distance=dep.distance(arr)) 
run(host='localhost', port=8888) 
A lancer par : 
python geoserver.py 
Tester l’url : http://localhost:8888/distance/48.85,2.35/43.30,5.38
Une autre approche : Producteur / Consommateur 
– Le code python peut appeler des méthodes C++ à 
distance 
– Par exemple via un bus RabbitMQ ou bien des 
appels distants ZMQ 
– Au final, le code Python délègue l‘exécution des 
sections critiques à des workers C++
Les difficultés en Python 
quand on vient du C++
Typage dynamique 
– En C++ le typage statique offre une première 
validation du code 
– En Python, on ne détecte les erreurs de type qu'à 
l'exécution ! 
 il faut exécuter le code pour le tester 
 les tests unitaires sont indispensables !
Threads 
– Le Global Interpreter Lock (GIL) assure que les traitements 
concurrents ne se marchent pas dessus : 
https://docs.python.org/2/glossary.html#term-global-interpreter-lock 
http://dabeaz.blogspot.fr/2010/01/python-gil-visualized.html 
– Les modules thread / threading 
– encapsulent les primitives système de thread 
– fournissent des primitives de synchronisation : verrous, 
sémaphores, etc. 
– ... Mais à aucun moment deux instructions peuvent être 
exécutées en même temps à cause du GIL ! 
 Mieux vaut utiliser multiprocessing
Mémoire partagée 
– Le module mmap (Memory-mapped file) permet de partager de 
la mémoire entre plusieurs process 
– On peut accéder à cette mémoire comme un fichier en lecture / 
écriture 
– On accède à cette mémoire comme un tableau de char 
– Mais on ne peut pas utiliser de types complexes (listes, 
dictionnaires, objets définis sur mesures) 
 Pas aussi souple qu’en C++
Conclusion
Où va Mappy ? 
– Amélioration continue de l’outillage 
– Nouveaux services asynchrones en Python 
– … Et bientôt un nouveau serveur d'itinéraire en Python / C++ ?
Merci 
Enjoy : http://fr.mappy.com/ 
Pierre Marquis : pierre.marquis@mappy.com 
Alexandre Bonnasseau : https://github.com/abonnasseau

Comment développer un serveur métier en python/C++

  • 1.
    Un serveur métieren Python / C++ Meet-up C++ 02/10/2014 Pierre Marquis & Alexandre Bonnasseau
  • 2.
    Plan • Retourd’expérience • En pratique, quelques lignes de code
  • 3.
    L'équipe dev carto Info trafic Itinéraire Plan Moteur de recherche géolocalisée Moteur de suggestion Géolocalisation Itinéraire transports en commun
  • 4.
  • 5.
    Python WTF ??! - Simplicité de code - Framework web léger - Richesse des modules - Facilité d'industrialisation
  • 6.
    Notre philosophie -Python quand on peut - C++ quand c’est nécessaire
  • 7.
    Chemin de l’équipe ● Fonctions plus atomiques ● Test U ● Lint ● Dojo ● Pair programming ● Code review
  • 8.
  • 9.
  • 10.
    Aujourd’hui + 80%des serveurs migrés + Langage très intéressant + Framework mieux maîtrisé + Dev plus rapide + Plus d’agilité - Moins de maîtrise C++ - Multithread - Débogage binding
  • 11.
    En pratique : appeler du C++ depuis Python
  • 12.
    Cadre – Existenced’une base de code C++ fiable et testée – On souhaite prototyper rapidement (besoin mouvant) – Besoin d’exécution rapide pour des fonctions critiques
  • 13.
    L'existant #define _USE_MATH_DEFINES #include <cmath> #include "geo.hpp" #include <sstream> #include <stdexcept> string geocoord2string(double angle) { if ( (angle > 180) || (angle < -180) ) throw invalid_argument("Invalid argument : angle must be beetween -180° and 180°"); int deg = int(floor(angle)); double rest = angle - deg; int minute = int(floor( rest * 60)); rest = rest * 60 - minute; int second = int(floor( rest * 60)); ostringstream result; result << deg << "° " << minute << "' " << second <<"''"; return result.str(); } double deg2rad(double deg) { return (deg * M_PI / 180); } GeoPoint::GeoPoint(double lat, double lng) : lat(lat), lng(lng) {} double GeoPoint::distance(const GeoPoint &other) { double nDLat = deg2rad(other.lat - this->lat); double nDLon = deg2rad(other.lng - this->lng); double thisLatRad = deg2rad(this->lat); double otherLatRad = deg2rad(other.lat); double nA = pow ( sin(nDLat/2), 2 ) + cos(thisLatRad) * cos(otherLatRad) * pow ( sin(nDLon/2), 2 ); double nC = 2 * atan2( sqrt(nA), sqrt( 1 - nA )); double nD = EARTH_RADIUS * nC; return nD; // Return our calculated distance }
  • 14.
    Objectif Utiliser lesfonctions C++ en python : $ python Python 2.7.3 >>> import geo >>> print geo.geocoord2string(5.36) 5° 21’ 36‘’ Projet complet disponible sur github : https://github.com/Mappy/un-serveur-metier-en-python-cpp
  • 15.
    Un module pythonécrit en C++ D'après : https://docs.python.org/2/extending/extending.html geomodule.cpp #include "Python.h" #include "geo.hpp" static PyObject * geocoord2string_py(PyObject *self, PyObject *args) { double angle = 0; if (!PyArg_ParseTuple(args, "d", &angle)) return NULL; string res = geocoord2string(angle); return Py_BuildValue("s", res.c_str()); } // La liste des fonctions qu'on expose en Python static PyMethodDef geoMethods[] = { { "geocoord2string", geocoord2string_py, METH_VARARGS, "Convert a latitude or a longitude as an angle in a string in the form : d° m' s''." }, { NULL, NULL, 0, NULL } /* Sentinel */ }; // La fonction initnomdumodule est appelée par l'interpréteur Python // à l'import du module PyMODINIT_FUNC initgeo(void) { (void) Py_InitModule("geo", geoMethods); }
  • 16.
    Avec Boost.Python ? geomudule.cpp #include <boost/python.hpp> #include <boost/python/module.hpp> #include "geo.hpp" using namespace boost::python; BOOST_PYTHON_MODULE(geo) { def("geocoord2string", geocoord2string); class_<GeoPoint>("GeoPoint", init<double, double>()) .def("distance", &GeoPoint::distance) … C’est tout ! ; }
  • 17.
    Avec Boost.Python, onpeut – Exposer des fonctions C++ en Python – Exposer des classes C++ en Python – Choisir quelles méthodes exposer pour une classe – Utiliser Boost::optional pour gérer les paramètres optionnels
  • 18.
    Packager une extensionPython Le fichier setup.py s’occupe de la compilation : from distutils.core import setup, Extension geo_module = Extension('geo', include_dirs = ['/usr/local/include',], library_dirs = ['/usr/local/lib',], extra_compile_args=['-std=c++11'], sources = ['geomodule.cpp', 'geo.cpp']) setup (name = 'Geo', version = '1.0', description = 'A geo package from Mappy', author = 'LBS team', author_email = 'lbs@mappy.com', url = 'http://github.com/mappy', long_description = ‘A demo package with geo functions', ext_modules = [geo_module]) Installer avec : python setup.py install Ou packager : python setup.py bdist
  • 19.
    Focus : Gestionde la mémoire – Un code C++ qui conserve une référence sur un objet Python doit appeler la macro Py_INCREF() pour s'assurer que l'objet ne sera pas détruit – Il faut appeler Py_DECREF() pour libérer l'objet
  • 20.
    Focus : Gestiondes erreurs Le code suivant lève une exception C++ : import geo print geo.geocoord2string(181) Sans Boost.Python : terminate called after throwing an instance of 'std::invalid_argument' what(): Invalid argument : angle must be beetween -180° and 180° Avec Boost.Python : Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: Invalid argument : angle must be beetween -180° and 180° Boost.Python permet également d'affiner la gestion des erreurs en associant des erreurs spécifiques Python aux exceptions C++
  • 21.
    Un serveur dedistance géographiques geoserveur.py from bottle import route, run, template from geo import GeoPoint @route('/distance/<lat1:float>,<lng1:float>/<lat2:float>,<lng2:float>') def index(lat1, lng1, lat2, lng2): dep = GeoPoint(lat1, lng1) arr = GeoPoint(lat2, lng2) return template("Distance = {{distance}}", distance=dep.distance(arr)) run(host='localhost', port=8888) A lancer par : python geoserver.py Tester l’url : http://localhost:8888/distance/48.85,2.35/43.30,5.38
  • 22.
    Une autre approche: Producteur / Consommateur – Le code python peut appeler des méthodes C++ à distance – Par exemple via un bus RabbitMQ ou bien des appels distants ZMQ – Au final, le code Python délègue l‘exécution des sections critiques à des workers C++
  • 23.
    Les difficultés enPython quand on vient du C++
  • 24.
    Typage dynamique –En C++ le typage statique offre une première validation du code – En Python, on ne détecte les erreurs de type qu'à l'exécution !  il faut exécuter le code pour le tester  les tests unitaires sont indispensables !
  • 25.
    Threads – LeGlobal Interpreter Lock (GIL) assure que les traitements concurrents ne se marchent pas dessus : https://docs.python.org/2/glossary.html#term-global-interpreter-lock http://dabeaz.blogspot.fr/2010/01/python-gil-visualized.html – Les modules thread / threading – encapsulent les primitives système de thread – fournissent des primitives de synchronisation : verrous, sémaphores, etc. – ... Mais à aucun moment deux instructions peuvent être exécutées en même temps à cause du GIL !  Mieux vaut utiliser multiprocessing
  • 26.
    Mémoire partagée –Le module mmap (Memory-mapped file) permet de partager de la mémoire entre plusieurs process – On peut accéder à cette mémoire comme un fichier en lecture / écriture – On accède à cette mémoire comme un tableau de char – Mais on ne peut pas utiliser de types complexes (listes, dictionnaires, objets définis sur mesures)  Pas aussi souple qu’en C++
  • 27.
  • 28.
    Où va Mappy? – Amélioration continue de l’outillage – Nouveaux services asynchrones en Python – … Et bientôt un nouveau serveur d'itinéraire en Python / C++ ?
  • 29.
    Merci Enjoy :http://fr.mappy.com/ Pierre Marquis : pierre.marquis@mappy.com Alexandre Bonnasseau : https://github.com/abonnasseau