Peut-on compiler un programme Python ?


Sans répit, des hordes d’OP ont demandé sur Stackoverflow comment compiler un programme Python. La plupart du temps, pour des raisons d’obfuscation ou pour faire un joli .exe. Et inlassablement, les devs bienveillants leur répondaient que Python était un langage interprété, et donc non compilable.

Commença alors la lente fuite des impatients vers Go qui permettait de le faire si facilement.

Et puis arriva Cython, qui promettait de permettre de transformer du Python en C, afin de l’embarquer dans un autre code C, ou permettre des appels de C vers Python et inversement plus facile.

Cela visait les perfs, l’intégration, faire des dll/so en Python, et du coup, dans l’excitation, tout le monde a loupé un petit détail minuscule.

Les mecs avaient implémenté un compilateur Python => C complet.

En fait, Cython permet – et c’est la que c’est fun car c’est un effet de bord presque involontaire – de créer un programme compilé autonome en Python. Et avec la toolchain classique en plus.

Ça peut gérer les dépendances, et ça marche même avec des trucs compilés balèzes du genre PyQT.

Je sens le chapiteau se dresser sous la braguette, alors voici la démo…

Pour montrer qu’on ne fait pas que du “Hello World”, je vais utiliser des dépendances assez complexes : lxml (qui contient une extension C compilée), path.py, requests et docopt :

"""
    Download a file and save it to a location.
 
    Usage:
        downloader.py <target> [<location>]
"""
 
import requests
import docopt
 
from lxml.html import parse
from path import path
 
args = docopt.docopt(__doc__)
 
p = path(args['<location>']).realpath()
 
if p.isdir():
    p = p / path(args['<target>']).namebase
 
p.write_bytes(requests.get(args['<target>']).content)
 
title = parse(p).getroot().find('head').find('title').text
print('Downloaded "%s"' % title)

Je ne m’encombre pas de gestion d’erreurs, juste suffisamment d’appels pour faire travailler les dépendances.

Qui dit compilation, dit environnement. Pour ma part, je suis sous Ubuntu 14.04 et je vais me compiler un petit programme en Python 3, avec lesquels il me faut installer Cython et pip pour Python 3, de quoi compiler du C, et les dépendances de notre script :

$ sudo apt-get install gcc cython3 python-pip3 python3-lxml
$ pip3 install requests docopt path.py

Je n’ai même pas eu besoin d’installer les headers de lxml. Vive le Dieu des geeks.

L’utilisation de Cython dans notre cas est assez simple : on lui dit de transformer notre module en module C. --embed lui dit d’inclure l’interpréteur Python dedans.

$ cython3 downloader.py -o downloader.c --embed

On obtient un fichier C bien copieux :

$ head downloader.c
/* Generated by Cython 0.20.1post0 (Debian 0.20.1+git90-g0e6e38e-1ubuntu2) on Sun May 11 22:53:18 2014 */
 
#define PY_SSIZE_T_CLEAN
#ifndef CYTHON_USE_PYLONG_INTERNALS
#ifdef PYLONG_BITS_IN_DIGIT
#define CYTHON_USE_PYLONG_INTERNALS 0
#else
#include "pyconfig.h"
#ifdef PYLONG_BITS_IN_DIGIT
#define CYTHON_USE_PYLONG_INTERNALS 1

Il ne reste plus qu’à compiler ce dernier :

$ gcc -Os -I /usr/include/python3.4m  downloader.c -o download -lpython3.4m -lpthread -lm -lutil -ldl

J’ai mis -I /usr/include/python3.4m et -lpython3.4m car les headers de Python sont là dedans sur ma machine. Le reste, ce sont des options que j’ai copier / coller sur le Web car GCC a plus de flags qu’une ambassade américaine et que j’ai des choses plus importantes à retenir, comme par exemple la recette du guacamole.

On obtient un exécutable tout à fait exécutablatoire :

$ chmod u+x downloader
$ ./downloader # docopt marche :)
Usage:
       downloader.py <target> [<location>]

Et ça télécharge comme prévu :

$ ./downloader http://sametmax.com index.html
Downloaded "Sam & Max: Python, Django, Git et du cul | Deux développeurs en vadrouille qui se sortent les doigts du code"

Le script Python original fait 472 octets, le binaire obtenu 28,7 ko. Mais ce n’est pas standalone, puisque je n’ai pas demandé la compilation des dépendances (je viens de le tester sur une autre Ubuntu, il pleure que requests n’est pas installé). Je vous laisse vous faire chier à trouver comment faire pour répondre à votre cas de figure exact, mais ça implique d’utiliser cython_freeze.

Après tout, cet article est intitulé “Peut on compiler un programme Python ?” et non “Tuto pour rendre un script Python stand alone”. Je ne suis pas fou.

Apparemment ça marche sous Windows et Mac, même si je n’ai pas de quoi tester sous la main (bon, si j’ai une partition Windows, mais faut rebooter, tout réinstaller, merde quoi…).

Donc si vous voulez faire une application en Python et la rendre stand alone, l’obfusquer, pondre un exe pour rendre le téléchargement facile, rassurer les gens ou simplement ignorer les mises à jours des libs de l’OS, Cython est une bonne piste.

Petit bonus, votre programme sera plus rapide, car il saute l’étape d’interprétation. Bien entendu, vous récoltez les galères qui viennent avec la compilation, à savoir les différentes architectures, le linking vers les libs qui peuvent changer de place ou de versions, etc.

33 thoughts on “Peut-on compiler un programme Python ?

  • Debnet

    Ca semble intéressant.
    Est-il possible de lui passer directement un dossier contenant une hiérarchie de module ? Genre un projet Django ?

  • kontre

    Et donc, la recette du guacamole ?

    @Debnet: si il choppe tout seul les dépendances et que ton arborescence est bien importée dans ton programme, je suppose que ça devrait le faire.
    Mais honnêtement si c’est pour les perf ça ne vaut pas le coup, car là où ça gagne vraiment c’est quand tu renseignes le type des variables et des arguments, pour utiliser les fonctions C directement.

    Ça serait cool de faire un article sur l’optimisation via cython, tiens. Sauf que je sais pas si c’est très efficace en dehors des applis scientifiques…

  • cendrieR

    Un article qui va trouver une bonne place dans les marques-pages. J’espère avoir bientôt l’occasion de tester. Merci !

  • Sam Post author

    @kontre: quelques avocats bien mous, une tomate très mûre, un oignon émincé au plus fin, du sel, du poivre, du curry + du cumin en poudre.

  • Roger

    “Commença alors la lente fuite des impatients vers Go qui permettait de le faire si facilement.”

    C’est exactement cela, en Go, un simple go build suffit.

    En Python bienvenue dans la galère :

    1 – Sur Windows, tu prends quelle version de Python 32 ou 64 ?
    2 – Ensuite la bonne version de Cython 32 ou 64 ?
    3- Pour finir tu prends quel MinGW le 32 ou 64 ?

    Et bien si tu prends tout en 64, ça ne marchera pas pour des raisons obscures de la version 64 de Python…

    Résultat tu remets tout en 32…bref t’en as pour 3 jours rien qu’à éplucher les listes pour voir quelles options pourries de gcc tu vas mettre. Donc tu finis avec un OS 64 et des applications 32 bits…

    Alors franchement générer un exe pour une application Python, c’est la galère et je vous passe les py2exe, cfreeze et pyinstaller où là aussi prévois 4-5 jours pour comprendre le bin’s et chercher toutes les erreurs du net ;)

    Alors oui bouge rapidos vers le Go ;)car distribuer une lib en Python c’est bien mais une appplication entière c’est la galère pas possible.

  • Sam Post author

    @Roger: je risque pas de bouger rapido vers le GO :) Y a trop de lacunes par rapport à Python pour mes usages.

  • Sam Post author

    Ca fait un baille que je me dis que je dois me mettre à kivy (après un Pycon Fr où j’avais rencontré un des core devs). Arf, qui a le temps ? Qui a le temps ?

  • Julien Hautefeuille

    @Sam : en France il y de forte chance que tu aies rencontré Mathieu Virbel ou Gabriel Pettier. La communauté Kivy est sympathique et d’un assez haut niveau en compétence technique Python et C. Mathieu possède de solides compétences en OpenGL à mon sens.

  • Julien Hautefeuille

    @sam : tu peux essayer Kivy, tu verras, c’est assez intéressant et ça reprend des concepts de Qt avec le QML (on a les signaux de Qt par exemple à travers les properties Kivy). Les cores dev sont des personnes besogneuses, le code est de qualité et ils aiment nous faciliter la vie pour produire des exe standalone ou des Apk Android configurées aux petits oignons. J’aime bien la mentalité, la haute conpétence technique et la créativité du framework Kivy.

  • Laurent

    Ma petite pierre, un bench bubble sort Python/Cython/Numba.

    On voit bien ce que ça implique en terme de code si on veut vraiment avoir un gain.

  • kontre

    Intéressant ton bench ! Il manque cependant un test avec numpy tout seul, et tu pourrais inclure la transformation de la liste en tableau numpy dans la fonction, pour une comparaison plus honnête. Et c’est bizarre la “lenteur” avec les memoryviews sur les petites listes.

    Par contre tu dis :

    On voit bien ce que ça implique en terme de code si on veut vraiment avoir un gain.

    Mais la solution la meilleure (pour les grosses listes) requiert très peu de modifs au final. Et ça donne toujours l’impression d’écrire du python.

    Mon plus gros souci avec numba, c’est déjà que c’est la merde à compiler, et surtout que dès que j’ai voulu tester sur des fonctions “réelles” y’avait toujours des trucs tout con mais pas supportés. Alors que cython est vraiment bien compatible avec python.

  • Yamakaky

    J’y pense, est-ce que cython permet de faire des jeux vidéos 3D performants en python ? Pour le coup, ce serait vraiment cool !

  • kontre

    Eve Online utilise python comme langage de script (comme World of Warcraft ou Wildstar utilisent Lua). Par contre le cœur n’est pas écrit en python, c’est pas fait pour ça.

    Cython est performant parce qu’il permet de ne pas utiliser les types python (appeler une fonction en python est “lent”) mais directement les fonctions et variables en C. Donc faire tout un programme en python, sans utiliser les types python, c’est quand même ballot…

  • Said

    En quoi golang ne peut répondre à tes besoins ? Tu vises spécialement le langage ou les frameworks ? Le fait qu’il ne prends pas en charge le polymorphisme parametric, l’overloading d’opérateur, les generics ? Ou que le design du langage peut changer d’une version à une autre étant donné sa relative jeunesse ?

    Car je suis sur un projet d’application web actuellement et je me tate à utiliser django qui est mature et vient avec un formidable langage, ou beego avec golang !

    La question que je me pose : est-ce viable de travailler sur un gros projet avec un langage dynamique ? Je trouve que c’est à s’arracher les cheveux pour savoir si la fonction retourne une valeur ou une list etc …

  • Sam Post author

    Je pense que si youtube, dropox, pinterest, instragram, disqus et addons.mozilla.org sont codés en Python, c’est viable :)

    Je n’utilise pas Go parce que :

    – il y a moins de libs en go qu’en Python
    – le langage est moins agréable à lire
    – il est encore jeune, il manque tout un écosystème autour (python existe depuis 20 ans)
    – il n’y a pas d’exceptions ni de sets en Go
    – je préfère le duck typing au typage fort pour la plupart des projets
    – python est déjà installé sur Linux et Mac sans rien à faire
    – c’est pas pratique pour faire du scripting
    – c’est pas pratique pour faire des interfaces graphiques
    – beaucoup moins de doc pour go
    – il n’y a aucun blog aussi bien que sametmax.com pour go

    Il y a des avantages à Go : les goroutines et la compilation en un exe facile. Mais ça ne m’a jamais bloqué jusqu’ici, même si j’avoues qu’il était temps que quelque chose comme autobahn sorte pour Python, ça commençait à bien faire.

    Mais la meilleure raison reste que j’aime Python, et il n’y a pas de motivations suffisante pour moi pour migrer. Une migration doit être motivée, sinon quel est le but ?

  • Said

    Je viens du monde .NET, et à l’époque de C# 3.5 il est été déjà facile de lancer une routine en asynchrone ! Mais depuis j’ai switché sur mac et je me suis mis à python/shell.

    J’entends souvent parler des goroutines mais honnêtement je suppose qu’en python il doit bien exister un moyen similaire ! Rassure moi ?

    Il me semblait que le duck typing était supporté par golang…

    Ce que je comprends pas, c’est que plusieurs groupes essaient d’améliorer l’interpréteur, notamement pypy, pyrex et nouvellement pyston de dropbox mais aucuns n’est utilisable en production. (je me doute que c’est un gros travail ). ça commence à faire beaucoup je trouve.

    Tout comme toi, je pense que python gagnerait à être plus connu si on pouvait construire un exécutable pour redistribuer un logiciel. Demain si je crée ma boite, je n’ai pas forcément envie de livrer mes sources !
    Sauf si bien sur, je crée une application web …

    Et toi tu n’es pas capable de nous pondre un builder ? :)

  • Sam Post author

    Il n’y a rien d’aussi pratique que les goroutines. On peut avoir les mêmes fonctionnalités, mais pas avec la même élégance en Python.

    Pypy est utilisé en production.

    On peut créer un exécutable avec pyinstaller, p2exe, cython, etc., c’est juste beaucoup moins facile qu’avec go.

  • vulcain

    Bonjour,

    En 64bits, python34 et cx_freeze ou p2exe pas moyen de compiler, j’ai testé un petit prog, simple 10 lignes (python34 + Tkinter) il ne trouve pas encodings et une liste d’erreur sur finder.py. Une galère python et pourtant un très bon langage. D’après ce que j’ai lu, p2exe n’est pas encore dispo pour python V3 en 64 bits. Et revenir en 2.7 pour du 32 bits non merci. alors si infos… merci

  • kontre

    Le packaging en python, c’est la merde.

    Sous Linux ça va encore, python est installé de manière globale, un script suffit.

    Sous windows c’est encore plus la mouise car il est obligatoire de créer un exécutable pour diffuser un programme. Ça devrait vraiment être natif…

  • vulcain

    Bonjour,

    Oui, je constate, je n’arrive pas à compiler et pourtant les lib appropriées sont présentes. J’espère qu’un jour cela va changer car le langage python est bon. J’ai vu aussi AutoIt comme langage mais je ne suis pas attiré. Le C++ mais c’est un langage qui ne se devine pas, il faut une formation. Donc actuellement, j’attends mais cela me rage.

  • BlackBeans

    Je travaille sur Debian jessie et cette dernière a changé le nom de python-pip3 en python3-pip…

    Trois jours de ma vie à éssayer de piger quel était le bon paquet… xD

  • ast

    Python est un langage dynamique, une fonction peut retourner une fonction, une classe … On peut écrire du code dans une string et l’exécuter avec exec(), et je ne vois pas comment on pourrait convertir en C un code dynamique puis le compiler

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.