Sam & Max » c 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 Lire un format binaire en Python avec struct 22 http://sametmax.com/lire-un-format-binaire-en-python-avec-struct/ http://sametmax.com/lire-un-format-binaire-en-python-avec-struct/#comments Fri, 26 Jun 2015 06:51:28 +0000 http://sametmax.com/?p=16503 Une suite de valeurs ne veut rien dire en soi, et même le sacro-saint binaire supposé être le socle de toute l’informatique n’a aucun sens si on ne connaît pas le format utilisé pour ce qu’il doit représenter.

Toujours la même opposition entre données et représentation.

Par exemple, le binaire peut représenter un chiffre en base 2 ou un texte encodé.

Pour autant, cela ne veut pas dire qu’il n’existe pas des formats prépondérant. En informatique, beaucoup de données binaires sont organisées pour correspondre aux structures de données du langage C, ces dernières étant une implémentation du standard IEEE 754 (en effet les strings sont des arrays d’int en C, donc le texte et les nombres sont des suites de chiffres).

Par exemple, si vous créez un array numpy contenant des nombres de 0 à 1000 stockés en int32 et sauvegardez son contenu dans un fichier :

>>> import numpy
>>> numpy.arange(0, 1000, dtype=np.int32).tofile('/tmp/data')

Le fichier va ici contenir une suite de 1 et de 0 représentant 1000 entiers, chacun comme un paquet de 4 octets organisés selon la sémantique que comprend le langage C.

Pour avoir une idée de l’organisation du contenu, on peut prendre un éditeur hexa qui vous affichera :

0000 0000 0100 0000 0200 0000 0300 0000 0400 0000 0500 0000 0600 0000 0700 0000 0800 0000 0900 0000 0a00 0000 0b00 0000 0c00 0000 0d00 0000 0e00 0000 0f00 0000 1000 0000 1100 0000 1200 0000 1300 0000

Ça se lit ainsi :

0000 0000 => 0
0100 0000 => 1
0200 0000 => 2
0300 0000 => 3
0400 0000 => 4
0500 0000 => 5
0600 0000 => 6
0700 0000 => 7
0800 0000 => 8
0900 0000 => 9
0a00 0000 => 10
0b00 0000 => 11
0c00 0000 => 12
0d00 0000 => 13
0e00 0000 => 14
0f00 0000 => 15
1000 0000 => 16
1100 0000 => 17
1200 0000 => 18
1300 0000 => 19
...

Numpy étant codé en C, cela semble plutôt logique qu’il dump tout ça dans ce format.

Mais c’est une représentation tellement courante que de nombreux formats standards l’utilisent. Par exemple, les archives et les images stockent souvent leurs données ainsi.

Prenez le format d’image PNG, la RFC indique que la taille de l’image est stockée dans le fichier sous la forme de deux entiers représentés par 4 octets chacun, ordonnés en big-endian, entre l’octet 16 et l’octet 24.

On peut donc récupérer ces informations en lisant son fichier image :

with open('image.png', 'rb') as f:
    taille = f.read(24)[16:24]

Le problème étant : comment lire cette info ? C’est un blob binaire qui ne veut rien dire pour Python :

print(taille)
b'\x00\x00\x07\x80\x00\x00\x048'

Le module struct est fait pour ça, on lui passe une donnée au format structure C, et il la convertit en type Python. Cela marche ansi, pardon, ainsi :

struct.unpack('motif_du_format_a_convertir', donnee)

Le format à convertir est une chaîne de caractères qui contient des symboles décrivant la structure de la donnée qu’on souhaite récupérer. Little-endian ou big-endian ? String, Int, Bool ?

Pour la taille de la photo, on sait qu’il y a deux entiers, non signés (une taille ne va pas être négative), en big-endian. D’après la doc de struct, on peut lui désigner un entier non signé avec ‘I’, et il faut les qualifier avec ‘>’ pour l’ordre big-endian. Du coup:

taille = struct.unpack('>II', taille)
print(taille)
(1920, 1080)

Il se trouve que mon image de test est un screenshot et que mon écran a une résolution de 1920×1080 :)

On peut faire l’opération inverse avec struct.pack, et bien entendu manipuler des formats plus complexes : il suffit de changer le motif qui représente le format à convertir.

]]>
http://sametmax.com/lire-un-format-binaire-en-python-avec-struct/feed/ 22
Embeder Python dans du C ou C++ 6 http://sametmax.com/embeder-python-dans-du-c-ou-c/ http://sametmax.com/embeder-python-dans-du-c-ou-c/#comments Wed, 14 Jan 2015 03:21:25 +0000 http://sametmax.com/?p=15727 documentée. Il est donc possible de créer des objets Python, charger un module Python ou exécuter une fonction Python depuis un code C et compiler tout ça.]]> L’implémentation de référence de Python est écrite en C, et son API est exposée et bien documentée. Il est donc possible de créer des objets Python, charger un module Python ou exécuter une fonction Python depuis un code C/C++ et compiler tout ça.

Mettons que j’ai dans un fichier biblio.py :

def yolo(arg):
    return arg.upper() + ' !!'

Je peux écrire un fichier prog.c qui l’utilise :

#include <Python.h>
 
int main () {
    // PyObject est un wrapper Python autour des objets qu'on
    // va échanger enter le C et Python.
    PyObject *retour, *module, *fonction, *arguments;
    char *resultat;
 
    // Initialisation de l'interpréteur. A cause du GIL, on ne peut
    // avoir qu'une instance de celui-ci à la fois.
    Py_Initialize();   
 
    // Import du script. 
    PySys_SetPath("."); // Le dossier en cours n'est pas dans le PYTHON PATH
    module = PyImport_ImportModule("biblio");
 
    // Récupération de la fonction
    fonction = PyObject_GetAttrString(module, "yolo");
 
    // Création d'un PyObject de type string. Py_BuildValue peut créer
    // tous les types de base Python. Voir :
    // https://docs.python.org/2/c-api/arg.html#c.Py_BuildValue
    arguments = Py_BuildValue("(s)", "Leroy Jenkins"); 
 
    // Appel de la fonction.
    retour = PyEval_CallObject(fonction, arguments);
 
    // Conversion du PyObject obtenu en string C
    PyArg_Parse(retour, "s", &resultat);
 
    printf("Resultat: %s\n", resultat);
 
    // On ferme cet interpréteur.
    Py_Finalize(); 
    return 0;
}

Là je mets du C, mais la seule vraie différence avec du C++, c’est qu’on aurait cout au lieu de printf.

Pour compiler tout ça, il faut les headers Python et un compilateur. Sous Ubuntu, c’est un simple :

sudo apt-get install python-dev gcc

Et on a tout nos .h dans /usr/include/python2.7. On gccise :

gcc -I/usr/include/python2.7 prog.c -lpython2.7 -o prog -Wall  && ./prog

Même pas un warning, c’est beau.

./prog
Resultat: LEROY JENKINS !!

C’est un exemple simple, mais comme vous le savez je suis une grosse burne en C, donc je ne pourrai pas porter l’expérience plus loin.

Je ne le recommande pas pour rendre votre programme scriptable en Python. Il vaut mieux permettre à Python d’appeler votre code C dans ce cas. Des outils comme cffi rendent cela beaucoup plus facile et rentable que tout faire tout le taff à la main. D’ailleurs si quelqu’un est chaud pour faire un tuto sur cffi…

Non, c’est plus dans le cas où vous avez un programme C, un programme Python, et votre programme C veut utiliser le programme Python sans avoir à tout réécrire. Ou pour le cas où vous avez décidé d’écrire une grosse partie de votre programme en Python pour profiter de sa productivité, mais que vous ne pouvez pas installer Python sur la machine sur laquelle vous aller installer le programme. Bon, y a PyInstaller et Nuitka pour ça également hein, donc tentez avant de tout embeder comme un bourrin.

Ça reste intéressant de voir les entrailles de CPython et à quel point il joue bien avec les langages bas niveaux.

]]>
http://sametmax.com/embeder-python-dans-du-c-ou-c/feed/ 6
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
Le choix d’un langage influence le fun de votre carrière 32 http://sametmax.com/le-choix-dun-langage-influence-le-fun-de-votre-carriere/ http://sametmax.com/le-choix-dun-langage-influence-le-fun-de-votre-carriere/#comments Tue, 14 May 2013 09:47:04 +0000 http://sametmax.com/?p=6095 Les langages de programmation sont censés être des technologies neutres, mais comme toute chose utilisée dans le monde réel pour des usages concrets et nombreux, l’humain finit par leur donner une orientation, une préférence.

De fait, chaque langage finit par être plus utilisé dans certains types de métiers ou d’activités, pour certains types de projets, dans certains environnements. Plus important encore, un langage appelle d’autres outils, et un certain type de collègue, et même si, comme d’habitude, la généralisation est un piège à con, il y a bien des stéréotypes visibles qui se dégagent.

Ainsi, Java et PHP sont des langages pour lesquels il est très facile de trouver du travail. Il y a une telle base de code là dehors qu’il y a des annonces partout, et tout le temps. En revanche, les environnements Java sont généralement très très lourds à manipuler. Pas en terme de performance (Java est aujourd’hui très rapide), mais en terme de charge de travail : énormément de configuration, beaucoup d’abstractions et de design pattern imbriqués, des APIs et des formats très verbeux…

Travailler dans un monde Java, c’est généralement travailler à un rythme lent, plus souvent dans des grosses boîtes, pour des systèmes assez larges avec un grosse inertie. Ne vous attendez pas à des Java-party avec vos collègues après le boulot.

PHP, c’est la même chose, avec des projets et composants plus simples (dans des entreprises de toutes tailles), et souvent bien plus dégueulasses. Non pas qu’on ne puisse pas écrire du PHP propre, mais 15 ans de PHP codé à l’arrache ne s’effacent pas avec quelques années de Symfony, et autre cakePHP. Du coup on hérite souvent d’un projet moche comme ta mère.

Bref, Java et PHP vous garantissent un job, mais les projets sont rarement marrants, et l’ambiance au taff sera pas pumped up. Par contre il y a de la doc et des tutos, même si généralement ceux pour PHP sont de meilleure qualité que pour Java (qui peuvent être assez indigestes).

Il y a une alternative intermédiaire : le C#. Beau langage, beaux outils, c’est propre sans être trop lourd, et la base de code est pas à vomir. Mais on reste dans le corporate, et la plupart du temps, sur du 98% Microsoft ou affiliés.

Après vous avez des langages spécialisés comme Erlang, Scala, Lisp. Là, trouver du taff sera généralement un challenge. Ce n’est pas le genre de truc qu’on voit partout. Et le niveau sera difficile : un débutant peut arriver avec la bite et le couteau dans un projet PHP, mais si vous vous pointez avec 3 mois d’expérience sur une gateway jabber haute tolérance, le bidouillage ça va pas le faire.

Ce sont des langages qu’il faut choisir si on aime le taff de haut niveau, la débrouillardise et les solutions intelligentes. En général les missions sont super intéressantes, mais la reconversion est dure. Par contre, vous rencontrerez des collègues qui valent le détour.

Puis il y a les langages de bas niveau, type C/C++. Aujourd’hui, c’est massivement des missions scientifiques ou de l’embarqué : analyse d’image, cartes électroniques, petites machines, etc. Il y a encore des trucs pas cool genre Windev (j’ai un pote coincé là dedans, c’est triste, fuyez ces annonces), mais ce n’est plus la majorité. Ces langages, c’est une question de tempérament. Est-ce que vous aimez la minutie ? L’efficacité des algos ? Le travail sous contrainte technique ? Si oui, alors ce genre de taff peut être très sympa. Sinon, choisissez un langage plus dynamique.

Ensuite il y a les langages de niches : Cobol, Power Builder, etc. Là c’est généralement des projets de merde très bien payés. Plus personne ne veut coder avec ces trucs, mais ça coûte trop cher à changer. Les jeunes sont acceptés, les formations offertes, le salaire de début de carrière est bon, mais par contre, le code est un truc de mémé. C’est pas mal pour commencer une carrière et se faire un peu de thune, mais faut savoir en sortir, et ça ne donne pas de bonnes habitudes en prog.

Et pour finir il y des langages dit “modernes” (ce qui est un abus… de langage, car ils ne datent pas d’hier) comme Python, Ruby ou Javascript. Le côté “moderne” vient surtout du fait qu’ils sont maintenant très adaptés à des projets modernes, où la souplesse de développement a priorité sur le reste des caractéristiques. Choisissez ces langages si vous visez des missions, principalement Web, sur des produits récents. Ce sont les communautés les plus sympas et dynamiques, ça brasse pas mal dans le milieu et c’est assez jeune. Mais un grand nombre de start up, donc il faut pas chercher la sécurité de l’emploi.

Une petite parenthèse pour Python tout de même : il n’est pas autant limité au Web et on le voit aussi beaucoup dans le secteur scientifique et bancaire. C’est sa versatilité et la facilité de reconversion qu’il offre qui me fait dire que c’est un très bon choix comme premier langage. Mais. Car il y a un “mais”. Trouver une offre Python sera plus difficile qu’une offre Ruby, et BEAUCOUP plus difficile qu’une offre PHP ou Java. Après perso je n’ai jamais connu le chômage.

Warning ! Il faut faire gaffe à se lancer dans le dev Web. C’est très tentant, mais contrairement aux autres carrières, ça veut dire une courbe d’apprentissage beaucoup plus longue. Vous ne pouvez pas juste apprendre votre langage, ses libs et son env et vous vendre comme “expert”.

J’ai laissé pas mal de truc de côté, comme l’objective C, Haskell, Smalltalk, le Bash, Perl et quelques milliers d’autres langages. On ne peut pas tout faire, et je voudrais surtout peindre un tableau global du marché. En me relisant, je me dis que c’est bourré d’idées reçues, qu’il y a plein d’exceptions, etc. Mais je pense que les infos données peuvent permettre à des gens qui se posent la question de l’orientation de prendre une décision moins mauvaise que “je lance un D20 et on verra bien”.

]]>
http://sametmax.com/le-choix-dun-langage-influence-le-fun-de-votre-carriere/feed/ 32
Appeler du code C depuis Python avec ctypes 28 http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/ http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/#comments Sun, 05 May 2013 06:55:40 +0000 http://sametmax.com/?p=5811 On vous a dit et répété que Python c’était un super langage de glu et que ça pouvait très facilement s’interfacer avec les binaires produits par du C. Mais jusqu’à quel point ?

En vérité c’est extrêmement simple : Python permet de se mapper directement sur un .dll ou un .so, et d’appeler n’importe quelle fonction qu’il contient depuis le code Python comme si c’était une fonction normale. Il n’y a rien à installer, c’est fourni d’office.

On peut tester ça facilement. Je ponds une lib d’une folle puissance grâce à mes talents de codeurs C internationalement connus dans le quartier :

#include<stdio.h>
 
/*
Attend un pointeur sur un array de caractères (une chaîne en C) 
et l'affiche.
*/
dit_papa(char * p)
{
    printf("%s\n", p);
}
 
 
/*
Attend deux entiers et les multiple
*/
multiplier(long a, long b)
{
    return a * b;
}
 
/*
Attend un pointeur de pointeur sur un array de char
parce qu'on aime les risques.
*/
jakadi(char ** p)
{
    printf("%s\n", *p);
}

On compile tout ça. Comme je suis sous Nunux, j’utilise GCC et j’obtiens un .so, mais sous Windows c’est pareil avec VisualStudio et les .dll.

gcc -shared -Wl,-soname,ZeLib -o ZeLib.so -fPIC ZeLib.c

ZeLib.so est prête et frétille d’impatience à l’idée de vous servir. Il ne reste plus qu’à lancer le shell Python…

D’abord on fait ses imports, c’est dans la lib standard tout ça :

>>> import ctypes

Ensuite on se bind sur le binaire, il faut préciser un chemin absolu sinon ça ne marche pas :

>>> zelib = ctypes.CDLL("/home/sam/Work/projet/ZeLib.so")

Et derrière on peut appeler une fonction (Python fait la conversion entre tous les types de bases Python et C) :

>>> res = zelib.multiplier(2, 3)
>>> print res
6

Si on veut faire des chaînes, on ne peut pas passer de l’unicode. Comme mon shell a toutes les chaînes en unicode par défaut, je dois encoder dans le charset de sortie (sur mon système, c’est UTF8):

>>> zelib.dit_papa("papa".encode('utf8'))
papa
5

Notez au passage que la fonction retourne quelque chose même si je n’ai pas précisé de valeur de retour. Du coup j’aurais mieux fait de mettre un bon return 0 à la fin.

Si on veut appeler une fonction qui attend un pointeur, il faut d’abord caster son type en un type C, puis appeler by_ref dessus, qui va passer l’argument par référence, plutôt que par valeur :

>>> from ctypes import *
>>> s = ctypes.c_char_p('kiwi'.encode('utf8'))
>>> zelib.jakadi(byref(s))
kiwi
5

Voilà.

Voilà, voilà.

Bon attention quand même, le C n’est pas aussi conciliant que le Python : le debug est plus dur (pas de stacktrace, mais un bon vieux core dump), pas de completion dans ipython, et on peut même planter la VM si on se débrouille bien :-) N’oubliez pas non plus que Python 64 bits ne peut pas taper dans des DLL 32 bits et inversement.

P.S: je ne vais pas mettre le code C à télécharger et le code Python, franchement, il est pas énorme. Donc petite exception dans cet article : y a rien à DL et la syntaxe est pas à base de comments.

]]>
http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/feed/ 28