Sam & Max » executable http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Peut-on compiler un programme Python ? 28 http://sametmax.com/peut-on-compiler-un-programme-python/ http://sametmax.com/peut-on-compiler-un-programme-python/#comments Sun, 11 May 2014 16:58:00 +0000 http://sametmax.com/?p=10245 C complet.]]> 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.

]]>
http://sametmax.com/peut-on-compiler-un-programme-python/feed/ 28