Logo officiel de Boost

Boost::Python, dates et conversions

Ceux qui ont déjà joué avec Boost::Python le savent : cette API est très complète mais malheureusement trop peu documentée. Si elle permet de rendre accessible du code C++ depuis un environnement Python avec parfois une déconcertante simplicité, certaines tâches triviales sont en revanche plus difficiles à réaliser qu’on aurait pu l’imaginer.

Si vous êtes sur cette page, c’est très probablement parce que vous aussi devez jouer avec des dates et des durées, de C++ vers Python, inversement ou bien les deux et que vous vous êtes vous aussi heurté à la complexité de Boost::Python.

L’article qui suit est le résultat de mes recherches en la matière, et la synthèse de la solution à laquelle je suis parvenu.

Le code de base

Pour l’ensemble de l’article, nous partirons du principe que vous utilisez la classe boost::posix_time::ptime côté C++, et datetime.datetime côté Python.

Remarque : Si jamais vous utilisiez une autre classe pour vos dates (quelle soit “maison” ou issue d’une autre bibliothèque), vous devriez pouvoir adapter le principe sans trop de problèmes.

Le code minimal pour un module Python avec Boost::Python dont nous partirons est le suivant :

Rien de fou donc, pour l’instant.

Ajoutons une fonction qui prend en paramètre et retourne un boost::posix_time::ptime :

Cette fonction ajoute bêtement 5 secondes à toute date qui lui est passée, sauf si celle-ci n’est pas une date valide.

Si le code actuel compile et donne une bibliothèque Python valide, l’appel de add_five_seconds() depuis l’interpréteur Python provoque la levée d’une exception :

En effet : à aucun moment nous n’avons indiqué à Boost::Python comment convertir une date Python en date C++, ni même que cette conversion était possible.

Pour que ceci fonctionne, il faut ajouter deux “converters” : un de C++ vers Python, et l’autre de Python vers C++.

Conversion de C++ vers Python

Si Boost::Python ne prend pas nativement en charge la conversion de date, il fournit néanmoins des outils puissants pour nous permettre d’y arriver.

Ajoutons le code de conversion à notre exemple précédent :

Analyse

Regardons ligne par ligne les changements apportés.

Le premier include est nécessaire pour utiliser boost::python::to_python_converter; le second pour rendre disponibles les types Python natifs, tels que PyObject ou PyDateTime.

Ici nous déclarons un “converter”. Au sens de Boost::Python, un “converter” est une simple structure ou classe qui contient une méthode statique nommée convert() qui prend un paramètre le type natif C++ à convertir, et qui retourne un PyObject*.

Dans le cas où notre date n’est pas une date valide, nous choisissons ici de renvoyer None. Libre à vous de modifier ce comportement pour satisfaire vos propres besoins.

Remarque : La valeur retournée doit avoir un compteur de référence strictement positif.

Enfin nous déclarons notre “converter” en spécifiant le type natif et la structure/classe à utiliser pour la conversion.

À partir de ce moment là, notre module Python sera capable de convertir implicitement tout boost::posix_time::ptime en datetime.datetime Python.

Conversion de Python vers C++

La conversion dans l’autre sens demande un peu plus de travail :

Analyse

Regardons encore une fois, ligne par ligne les modifications apportées :

Nous ajoutons une structure qui va contenir les routines de conversions. Contrairement à tout à l’heure, et comme nous le verrons plus tard, ceci n’est pas indispensable. Pour effectuer des conversions de Python vers C++, Boost::Python a juste besoin de deux fonctions. Que celles-ci soient statiques au sein d’une classe ou libres n’a aucune incidence.

La fonction is_convertible() sera utilisée par Boost::Python pour déterminer si l’instance Python à convertir peut l’être.

Dans notre cas nous acceptons tout d’abord None comme valeur “valide” pour respecter la symétrie, puis nous testons si l’instance est de type datetime.datetime grâce à la fonction PyDateTime_Check().

En cas de succès, nous renvoyons la valeur passée en paramètre telle quelle. En cas d’erreur, nous renvoyons NULL.

C’est ici que se passe le gros du travail : lorsque cette fonction est appelée, cela signifie que l’instance obj_ptr a passé l’appel à is_convertible() et est prête à être convertie.

Le paramètre data lui contient entre autres l’adresse de la zone mémoire où nous devons instancier notre résultat de conversion. Notez que pour ce faire, nous utilisons le “placement new” qui permet de construire une instance à un emplacement mémoire donné. Le delete correspondant sera automatiquement appelé par Boost::Python au besoin.

Pour finir, nous renseignons le champ convertible du paramètre data pour y indiquer où nous avons alloué notre résultat.

Comme auparavant, la dernière étape consiste à enregistrer le “converter” pour le faire connaître de Boost::python.
On remarque ici que comme énoncé précédemment, l’appel prend en paramètre deux fonctions qui n’ont pas nécessairement besoin de faire partie d’une classe ou d’une structure.

Résultat

Compilez le code ci-dessus (voir le script SConstruct en annexe), puis chargez votre module au sein de l’interpreteur Python :

Ça y est ! La conversion boost::posix_time::ptime <=> datetime.datetime fonctionne parfaitement. :)

Conclusion

Boost::Python est définitivement une bibliothèque très puissante. On peut certes regretter la qualité de sa documentation, mais fort heureusement les ressources à son sujet sur l’Internet ne manquent pas. Son extensibilité la rend utilisable dans toutes les situations et facilite grandement la vie du développeur.

J’espère que cet article vous aura aidé et/ou donné envie de découvrir/utiliser Boost::Python. Comme toujours, n’hésitez pas à me signaler toute coquille, erreur ou optimisation qui m’aurait échappé.

Annexe

Voici le script SConstruct que j’ai utilisé pour la compilation :

Sources

Ces pages m’ont été très utiles lors de la rédaction de cet article :

  • L’API datetime sur python.org (en Anglais);
  • l’API Boost::Posix Time sur boost.org (en Anglais);
  • l’API de Boost::Python sur boost.org (en Anglais);
  • cet article de misspent (en Anglais).

4 réflexions au sujet de « Boost::Python, dates et conversions »

  1. daminetreg

    Article très intéressant, moi qui brûle d’envie de me mettre au Python, si en plus je peux le lier au C++ d’une manière légère comme celle-ci cela ne peut que m’attirer encore plus.

    En ce qui concerne les conversions je pense qu’il y a moyen de générer ce code là (en faisant ses propres outils), j’essaierai de voir ce qu’il y a à faire dans cette voie là quand je me mettrai au Python. :)

    Répondre
    1. Julien Kauffmann Auteur de l’article

      Merci beaucoup. :)

      En fait, je ne pense même pas que ça soit nécessaire : je montre ici comment ajouter un type non supporté de base par Boost::Python donc c’est un peu verbeux, mais la plupart des types usuels (std::string, types ordinaux, chaînes C, etc.) sont déjà implicitement convertis, et dans les deux sens.

      Et si tu comptes te mettre au Python, je te recommande chaudement http://diveintopython.org/, qui est un bouquin excellent !

      Répondre
  2. daminetreg

    Merci pour le lien, je vais me l’acheter pour apprendre un langage rien de mieux qu’un livre imprimé.

    Je pense que le python peut beaucoup m’apporter en tant que langage pour la réalisation de scripts d’automatisation des builds, déploiements etc. :)

    Ton exemple est très bon notamment pour nos propres types. :) Si seulement un pont de cette qualité là existait entre C++ et Java, parce que je trouve cela toujours un peu bricolage les JNI.

    Répondre
    1. Julien Kauffmann Auteur de l’article

      Pour les build, je te le confirme. Jette un oeil du côté de SConstruct, que je trouve très bien foutu. Surtout en comparaison avec Make…

      C’est fait en Python, c’est extensible et franchement la gestion des dépendances est un vrai plaisir.

      Tout à fait d’accord avec ta remarque pour JNI : j’ai justement eu à réaliser une série de wrappers Python et JNI à partir de la même bibliothèque en C++ : je te laisse deviner laquelle j’ai pu écrire en une journée et laquelle en a pris quatre. ;)

      Répondre

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">