PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Pouvez-vous séparer le cul de la programmation SVP ?

mardi 16 juillet 2013 à 10:33

Régulièrement, par twitter, mail ou en comment, on reçoit quelque chose qui ressemble à ça :

Bonjour !

En rapport avec votre dernier article et NSFW toussa toussa, je sais que vous êtes contre la création d’une catégorie NSFW mais est-ce qu’il serait possible de ne pas poster du cul dans la catégorie “programmation” – ou plutôt ne pas mettre de flag programmation dans ce genre de cas ? Vous feriez un heureux.

Parce que si l’image gay était marrante, les images en commentaires étaient moyenne et j’aimerais pouvoir continuer à consulter votre blog que j’apprécie depuis mon boulot.

Sinon, merci pour tous vos articles, j’ai appris beaucoup de choses grâce à vous

En espérant que vous me comprendrez.

Les gens sont toujours très polis et respectueux dans leur demande, et on l’apprécie beaucoup. En fait, même quand l’AFPY nous a annoncé qu’ils nous viraient, ils l’ont fait dans le respect. C’est comme ça que les choses doivent se faire.

Et c’est donc comme ça que je vais essayer de répondre.

Non

Nous mélangeons le cul et la programmation parce que cela nous amuse. Nous écrivons ce blog pour le plaisir. Devoir réfléchir à ce qu’on y met, comment on l’y met, et aller à l’encontre de notre spontanéité pour des raisons autres que pédagogiques irait à l’encontre de cela.

Comprenez bien, tous les jours nous vivons l’intolérance de personnes qui voient le sexe comme quelque chose de mal, à cacher, mais qui par contre n’ont rien contre la violence.

Personnellement (je pense que Max s’en branle un peu plus que moi), j’utilise le blog comme moyen de faire passer ce message : on peut mélanger la double pénétration et la compétence technique, l’osé et le casual, l’arrache et le recherché.

Si vous ne pouvez pas consulter ce blog au travail, le problème ne vient pas du blog.

Il vient de votre lieu de travail.

Le blog lui, est ce qui l’est, avec sa formule annoncée dès le départ.

Vous ne pouvez pas consulter ce blog à cause de cela ? Ce n’est pas grave, ne consultez pas le blog. Il n’y a pas mort d’homme. Internet est grand, le monde aussi. Il y a de la place pour tout, des ressources en masse et des domaines qui conviendrons mieux à vos contraintes.

Personnellement si je travaillais dans un lieu qui m’empêchait d’accéder à des contenus que je considère utile, je changerais de travail.

Sans rancune. Sans jugement. Simplement parce que cela fait partie des choses qui sont importantes pour moi.

Si vous ne voulez pas faire cela, alors c’est que ce travail vous apporte plus que ce que vous retire cette contrainte, et c’est très bien ainsi.

Mais ne nous demandez pas de changer le blog, ça na pas de sens.

Par ailleurs, ce qui me dérange dans ces demandes, c’est que leurs auteurs n’envisagent pas de faire un effort de leur côté, et nous demandent d’en faire un. Le blog est sous Creative Common, quelqu’un pourrait sans problème en faire une version SFW. Il existe un dump du blog, il est donc consultable hors ligne. On peut aussi imaginer que la personne lise uniquement le blog en dehors du bureau. Ou alors qu’elle s’installe un proxy anonymisant. On peut aussi désactiver les images dans un Firefox, pour éviter de se faire griller en Open Space, ou utiliser Lynx. On peut aussi en parler à ses collègues et ses boss, et régler le problème avec le dialogue. Bref, il y a des tas de solutions à ce problème, et étant donné que nous fournissons un travail colossal avec ce blog (on en est à 500 articles, mine de rien, ça fait presque deux bouquins, en moins de 2 ans), je crois qu’il est important que vous vous responsabilisiez un peu.

Oh, et non, je ne pense pas aux enfants. Les enfants ne sont pas traumatisés par la pornographie, les enfants sont traumatisés par les adultes qui sont gênés en parlant de sexe et qui ne sont pas capables de remettre les choses dans leur contexte.

Ceci n’est pas un article destiné à être vindicatif ou moraliste. Je comprends parfaitement la position des personnes qui nous font ces demandes. Comprenez vous aussi, que c’est non.

flattr this!

plist, pickle, hdf5, protocol buffers… : les formats binaires

lundi 15 juillet 2013 à 21:24

Dans un article précédent, on avait fait un petit tour des formats texte, et j’avais promis qu’on verrait les formats binaires.

Contrairement à cette fois là, je vais faire un peu plus technique, et donc il est probable que ça ne parle pas aux non informaticiens.

Avant toute chose, il faut faire un peu de ménage. En effet, tous les formats de données sont des formats binaires, même les formats texte. Quand bien même on retire les formats texte par convention, tout le reste sont des formats binaires.

tar.gz, zip, 7zip, rar, iso, dmg et compagnie sont des formats binaires. Il servent à l’archivage.

doc, xls, ppt, pps, etc. sont des formats binaires. Ils servent à sauvegarder un document édité sous une suite Microsoft Office.

jpg, tiff, png, gif ou webp sont des formats binaires. Ils servent à représenter des images.

wav, mp3, ogg, acc, opus et monkey sont des formats binaires. Ils servent à stocker des données sonores.

mkv, avi, mov, mp4, ogm, webm… sont des formats binaires. Ils servent à contenir des informations vidéos.

Bref, tout fichier est un format binaire, toute donnée transmise d’un système informatique à un autre est un format binaire.

Alors qu’est-ce qu’on entend ici par “format binaire” ?

Principalement, format de sérialisation binaire.

En effet JSON, XML ou CSV sont avant tout, bien que pas uniquement, des formats de sérialisation, et nous allons donc voir des équivalents dans le monde du binaire. Attention cependant, il existe de centaines de formats, et beaucoup sont très utilisés même si je n’en ai jamais entendu parler. Les formats de sérialisation binaires sont en effet moins universels, c’est à dire qu’on les retrouve plus souvent liés à un usage ou un corps de métier. Les scientifiques ont les leurs, les industriels les leurs, les concepteurs d’OS les leurs, les constructeur de matériel les leurs, etc. Le fait que je ne les connaisse pas ne veut pas du tout dire qu’ils ne sont pas massivement utilisés. Cela veut juste dire que je ne les ai jamais croisés dans mon activité.

Par ailleurs je ne présenterai pas tous ceux que j’ai effectivement croisés. Voyez l’article comme une base de travail qui va vous permettre d’évaluer les autres formats binaires plutôt qu’un listing exhaustif.

En théorie, on distingue des données binaires, et des données encodées en binaire. En pratique, on s’en branle.

Séria-quoi ?

A la conception d’un programme se pose la question de savoir comment stocker ses données dans un fichier ou les transmettre par le réseau. Vous avez vos données sous forme de code, par exemple en Python une collections d’instances de vos propres classes, des dictionnaires, des listes, des entiers, des chaînes, etc. Ces objets, il va falloir les transformer en quelque chose qui puisse sauvegardé dans un fichier. Ou envoyé sur le réseau.

Cette opération de transformation, c’est ce qu’on appelle la sérialisation.

Quand on lit le fichier ou que l’on récupère la donnée via un réseau, on doit la transformer pour obtenir des objets manipulables sous forme de code : les collections d’instances de vos propres classes, des dictionnaires, des listes, des entiers, des chaînes qui étaient là à l’origine.

Cette opération de transformation, c’est ce qu’on appelle la dé-sérialisation.

Prenons un exemple en Python. J’ai une classe Personne() :

>>> class Personne(object):
...    def __init__(nom, age):
...         self.nom = nom
...         self.age = age

Et j’ai un calendrier qui liste les personnes présentes selon les jours de la semaine :

>>> gertrude = Personne("Gertrude", 18)
>>> monique = Personne("Monique", 12)
>>> jenifer = Personne("Jenifer", 97)
>>> cal = {
"lundi": [gertrude],
"mardi": [gertrude, monique],
"mercredi": [],
"jeudi": [monique],
"vendredi": [gertrude, jenifer],
"samedi": [gertrude, monique, jenifer],
"dimanche": [gertrude]
}

On a donc un format riche ici, avec plusieurs types imbriqués : du dico, de la liste, de l’instance de classe perso, de l’entier et des strings. On a donc des primitives, des données associatives, des séquences ordonnées et un structure complexe.

Pour sauvegarder ça dans un fichier ou le faire passer sur un réseau, il va falloir écrire un sacré bout de code. Par exemple si vous voulez le transformer en XML ou en JSON, il n’y a pas de type “Personne” dans ces formats. Il va donc falloir vous mettre d’accord sur une convention, écrire le code qui génère les données formatées selon cette convention, et également le code qui permet de lire ces données formatées et recréer les bons objets derrière. Sans parler du fait que la techno qui écrit ne va peut être pas être celle qui lit. C’est ça, la problématique de la sérialisation.

Les formats binaires se prêtent bien au jeu de la sérialisation, bien qu’ils puissent, eux aussi, servir à bien d’autre chose. Il sont compacts, et non limités par un besoin de lisibilité, ils contiennent souvent des moyens de contenir des données au format complexe. Ils sont aussi en général rapides à traiter, et prennent peu de place.

Pickle

Pickle est un format de sérialisation spécialisé pour Python. Seul un programme Python peut écrire et lire du Pickle, même si des projets existent pour faire le pont avec d’autres langages.

Voilà ce que ça donne à l’usage, en reprenant notre calendrier précédent :

>>> import pickle
>>> pickle.dumps(cal)
"(dp0\nVmardi\np1\n(lp2\nccopy_reg\n_reconstructor\np3\n(c__main__\nPersonne\np4\nc__builtin__\nobject\np5\nNtp6\nRp7\n(dp8\nS'nom'\np9\nVGertrude\np10\nsS'age'\np11\nI18\nsbag3\n(g4\ng5\nNtp12\nRp13\n(dp14\ng9\nVMonique\np15\nsg11\nI12\nsbasVsamedi\np16\n(lp17\ng7\nag13\nag3\n(g4\ng5\nNtp18\nRp19\n(dp20\ng9\nVJenifer\np21\nsg11\nI97\nsbasVvendredi\np22\n(lp23\ng7\nag19\nasVjeudi\np24\n(lp25\ng13\nasVlundi\np26\n(lp27\ng7\nasVdimanche\np28\n(lp29\ng7\nasVmercredi\np30\n(lp31\ns."

Ce blougi blouga est une représentation sérialisée de notre calendrier. Si vous le sauvegardez dans un fichier ou que vous l’envoyez à un autre programme Python, il peut récupérer les objets initiaux :

>>> cal2 = pickle.loads("(dp0\nVmardi\np1\n(lp2\nccopy_reg\n_reconstructor\np3\n(c__main__\nPersonne\np4\nc__builtin__\nobject\np5\nNtp6\nRp7\n(dp8\nS'nom'\np9\nVGertrude\np10\nsS'age'\np11\nI18\nsbag3\n(g4\ng5\nNtp12\nRp13\n(dp14\ng9\nVMonique\np15\nsg11\nI12\nsbasVsamedi\np16\n(lp17\ng7\nag13\nag3\n(g4\ng5\nNtp18\nRp19\n(dp20\ng9\nVJenifer\np21\nsg11\nI97\nsbasVvendredi\np22\n(lp23\ng7\nag19\nasVjeudi\np24\n(lp25\ng13\nasVlundi\np26\n(lp27\ng7\nasVdimanche\np28\n(lp29\ng7\nasVmercredi\np30\n(lp31\ns.")
>>> type(cal2)
<type 'dict'>
>>> for jour, personnes in cal2.items():
...     print(jour)
...     for personne in personnes:
...         print("\t- {}".format(personne.nom))
...
mardi
    - Gertrude
    - Monique
samedi
    - Gertrude
    - Monique
    - Jenifer
vendredi
    - Gertrude
    - Jenifer
jeudi
    - Monique
lundi
    - Gertrude
dimanche
    - Gertrude
mercredi

On utilisera Pickle essentiellement par fainéantise, quand on veut sauvegarder des objets Python et qu’on souhaite les récupérer plus tard, mais qu’on ne veut pas coder un code de sérialisation. Il existe des formes hybrides de cette approche, comme cette lib qui essaye de mélanger JSON et une forme de sérialisation d’objets complexes.

Quel que soit l’approche choisit, restaurer des objets complets, et non juste des primitives, comporte sont lot de risques de sécurité. En effet, un fichier Pickle malicieux sera exécuté comme code Python valide sans aucune vérification.

A noter que Python vient avec un autre format de sérialisation : marshall. Il est utilisé par Python en interne pour les fichiers .pyc et n’est pas recommandé pour un usage de persistance de données car le format évolue avec les versions de Python.

plist

Il existe de nombreux formats binaires qu’utilisent les OS comme .DS_store ou Thumbs.db. plist est l’un deux, et on va le voir parce qu’il est relativement simple à comprendre par rapport aux autres. Le principe est le même pour tous : on a des données, on les stock dans le fichier.

plist est un format qui existe aujourd’hui en XML, preuve que le même rôle peut très bien être rempli par deux formats différents. Il sert à stocker les réglages qu’on effectue dans le finder de Mac OS X, et ceux pour chaque dossier. Il sait représenter les types suivant : string, nombre, boolean, date, array, dictionnaire et des données arbitraires en base64 (un encodage binaire représentable sous forme de texte. Qu’est-ce qu’on se marre ^^).

Ce qui signifie par exemple, qu’il n’est pas capable de représenter un objet Personne() tel quel. Par contre il a des équivalents des types list, int, str, etc, ce qui en fait un format facile à manipuler en Python, surtout étant donné que la lib standard contient un module pour ça :

gertrude = ("Gertrude", 18)
monique = ("Monique", 12)
jenifer = ("Jenifer", 97)
cal = {
"lundi": [gertrude],
"mardi": [gertrude, monique],
"mercredi": [],
"jeudi": [monique],
"vendredi": [gertrude, jenifer],
"samedi": [gertrude, monique, jenifer],
"dimanche": [gertrude]
}
 
>>> gertrude = ("Gertrude", 18)
>>> monique = ("Monique", 12)
>>> jenifer = ("Jenifer", 97)
>>> cal = {
... "lundi": [gertrude],
... "mardi": [gertrude, monique],
... "mercredi": [],
... "jeudi": [monique],
... "vendredi": [gertrude, jenifer],
... "samedi": [gertrude, monique, jenifer],
... "dimanche": [gertrude]
... }
>>> plistlib.writePlistToString(cal)
'<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n\t<key>dimanche</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Gertrude</string>\n\t\t\t<integer>18</integer>\n\t\t</array>\n\t</array>\n\t<key>jeudi</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Monique</string>\n\t\t\t<integer>12</integer>\n\t\t</array>\n\t</array>\n\t<key>lundi</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Gertrude</string>\n\t\t\t<integer>18</integer>\n\t\t</array>\n\t</array>\n\t<key>mardi</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Gertrude</string>\n\t\t\t<integer>18</integer>\n\t\t</array>\n\t\t<array>\n\t\t\t<string>Monique</string>\n\t\t\t<integer>12</integer>\n\t\t</array>\n\t</array>\n\t<key>mercredi</key>\n\t<array>\n\t</array>\n\t<key>samedi</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Gertrude</string>\n\t\t\t<integer>18</integer>\n\t\t</array>\n\t\t<array>\n\t\t\t<string>Monique</string>\n\t\t\t<integer>12</integer>\n\t\t</array>\n\t\t<array>\n\t\t\t<string>Jenifer</string>\n\t\t\t<integer>97</integer>\n\t\t</array>\n\t</array>\n\t<key>vendredi</key>\n\t<array>\n\t\t<array>\n\t\t\t<string>Gertrude</string>\n\t\t\t<integer>18</integer>\n\t\t</array>\n\t\t<array>\n\t\t\t<string>Jenifer</string>\n\t\t\t<integer>97</integer>\n\t\t</array>\n\t</array>\n</dict>\n</plist>\n'

Bon, là j’ai un peu foiré mon exemple parce que la lib standard, elle pond la version XML (puisque la version binaire est obsolète), pas la version binaire de plist, et maintenant que j’ai écris tout ça, ça me fait chier de tout refaire. Heureusement j’ai trouvé une lib sur le net qui va sauver mon honneur :

>>> biplist.writePlistToString(cal)
'bplist00bybiplist1.0\x00\xd7\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0ee\x00m\x00a\x00r\x00d\x00if\x00s\x00a\x00m\x00e\x00d\x00ih\x00v\x00e\x00n\x00d\x00r\x00e\x00d\x00ie\x00j\x00e\x00u\x00d\x00ie\x00l\x00u\x00n\x00d\x00ih\x00d\x00i\x00m\x00a\x00n\x00c\x00h\x00eh\x00m\x00e\x00r\x00c\x00r\x00e\x00d\x00i\xa2\x0f\x10\xa2\x11\x12h\x00G\x00e\x00r\x00t\x00r\x00u\x00d\x00e\x10\x12\xa2\x13\x14g\x00M\x00o\x00n\x00i\x00q\x00u\x00e\x10\x0c\xa3\x15\x16\x17\xa2\x11\x12\xa2\x13\x14\xa2\x18\x19g\x00J\x00e\x00n\x00i\x00f\x00e\x00r\x10a\xa2\x1a\x1b\xa2\x11\x12\xa2\x18\x19\xa1\x1c\xa2\x13\x14\xa1\x1d\xa2\x11\x12\xa1\x1e\xa2\x11\x12\xa0\x15$/<MXct\x85\xb2\xd0\xd9\xde\xe3\xe8\x88\x9e\x8b\x9c\xa1\xb0\xb6\xb9\xbc\xbf\xce\xd3\xd6\xdb\xe0\xe5\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9'
>>> biplist.readPlistFromString(r'bplist00bybiplist1.0\x00\xd7\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0ee\x00m\x00a\x00r\x00d\x00if\x00s\x00a\x00m\x00e\x00d\x00ih\x00v\x00e\x00n\x00d\x00r\x00e\x00d\x00ie\x00j\x00e\x00u\x00d\x00ie\x00l\x00u\x00n\x00d\x00ih\x00d\x00i\x00m\x00a\x00n\x00c\x00h\x00eh\x00m\x00e\x00r\x00c\x00r\x00e\x00d\x00i\xa2\x0f\x10\xa2\x11\x12h\x00G\x00e\x00r\x00t\x00r\x00u\x00d\x00e\x10\x12\xa2\x13\x14g\x00M\x00o\x00n\x00i\x00q\x00u\x00e\x10\x0c\xa3\x15\x16\x17\xa2\x11\x12\xa2\x13\x14\xa2\x18\x19g\x00J\x00e\x00n\x00i\x00f\x00e\x00r\x10a\xa2\x1a\x1b\xa2\x11\x12\xa2\x18\x19\xa1\x1c\xa2\x13\x14\xa1\x1d\xa2\x11\x12\xa1\x1e\xa2\x11\x12\xa0\x15$/<MXct\x85\xb2\xd0\xd9\xde\xe3\xe8\x88\x9e\x8b\x9c\xa1\xb0\xb6\xb9\xbc\xbf\xce\xd3\xd6\xdb\xe0\xe5\x00\x00\x00\x00\x00\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xe9')
{u'mardi': [('Gertrude', 18), ('Monique', 12)], 'samedi': [('Gertrude', 18), ('Monique', 12), ('Jenifer', 97)], 'vendredi': [('Gertrude', 18), ('Jenifer', 97)], 'jeudi': [('Monique', 12)], 'lundi': [('Gertrude', 18)], 'dimanche': [('Gertrude', 18)], 'mercredi': []}

Pourquoi utiliser plist ? A part quand on est en Objectif-C où c’est le format le plus simple à parser ou si on veut communiquer avec finder, il n’y a pas vraiment de raison. C’est le cas typique d’un format qui a été créé parce qu’à l’époque il n’y avait rien d’aussi bien, les parsers XML étaient alors trop lents pour scanner toutes les plist de tous les dossiers récursivement.

hdf5

hdf5 est très intéressant, c’est le cas typique d’un format qui existe pour un usage très très particulier, et que des formats ordinaires ne comblent pas, ne peuvent pas par nature combler. C’est un format cross-plateforme qui peut contenir de très grosses quantités de données numériques (un fichier peut avoir une taille virtuellement illimitée), et les manipuler pour faire des calculs complexes. Cela ressemble à un système de fichiers… qui tient dans un fichier. En effet, il peut contenir une arborescence de données, et gère la compression transparente, mais les données sont essentiellement des arrays à plusieurs dimensions, appelés ici datasets.

On peut y mettre des arrays, des labels, des attributs, organiser tout ça par groupe et même avoir des références vers des données extérieures. L’avantage c’est qu’on peut bosser dessus presque de manière transparente, comme si c’était en RAM. Tout ce qui est array est stocké tel quel, et donc très rapide d’accès (bien plus qu’une colonne de base SQL), pour le reste, c’est indexé avec arbre binaire, donc facilement triable.

Pour manipuler ce format avec Python, on va utiliser la lib h5py :

sudo apt-get install libhdf5-serial-dev python-dev # sur ubuntu en tout cas
pip install numpy
pip install h5py

La normalement, ça compile à mort pendant 10 minutes.

Et pif paf pouf :

>>> import numpy # hdf5 s’utilise beaucoup avec les libs scientifiques type numpy
>>> import h5py
>>> array = numpy.ones((1000000000, 1000000000)) # une grosse matrice
>>> f = h5py.File(‘data.hdf5′)
>>> dset = f.create_dataset(“Nom du dataset”, data=array)
>>> dset

>>> f.close()

Et voilà, on vient de créer array contenant 1000000000 lignes de 1000000000 de 1000000000 de int ayant pour valeur “1″, et stocké tout ça dans un fichier au format hdf5. Ca prend quelques secondes, et le fichier fait quand même 800 Mo !

On le voit ici, hdf5 est entre le format de sérialisation et la base de données, et il est très orienté chiffre. Il existe tout un tas de formats binaires spécialisés pour un usage en particulier comme hdf5, à votre charge, donc, de chercher si il en existe un pour le votre. Ou même si vous en avez besoin d’un.

Des libs de haut niveau ont été construite en utilisant hdf5, telles que pytables, qui permettent de traiter très facilement d’énormes jeux de données tabulaires.

Protocol Buffers

Aussi appelé protobuf par ses amis, c’est un format de sérialisation inventé par Google qu’il utilise pour communiquer entre ses machines. On a donc vu un format de sérialisation orienté persistance avec Pickle, un orienté configuration, un orienté “grosse quantité de données” et voilà un dernier orienté communication réseau.

Protocol Buffers est un espèce d’hybride, puisqu’il utilise une description du schéma pour générer du code qui va sérialiser les donner en binaire. Vous suivez ? Non ?

Attendez ça va devenir plus clair.

Reprenons notre bonne vielle personne. Pour utiliser protobuf, vous allez décrire à quoi ressemble votre personne, dans un format texte spécialement conçu :

message Personne {
  required string nom = 1;
  required int32 age = 2;
}

Vous constatez qu’on décrit ici un message, qui va devoir contenir au minimum un nom et un age, de type string et entier. Les chiffres représentent des identifiants uniques de champs qui seront utilisés dans le message binaire.

Ceci n’est pas du code d’un langage particulier, c’est la syntaxe de modèle de protobuf.

On sauvegarde tout ça dans un fichier personne.proto, et on utilise la commande protoc pour transformer cette description en code dans le langage de son choix. C++ et Java sont supportés, nous on va utiliser Python :

protoc personne.proto --python_out=.

Et il va nous pondre un fichier personne_pb2.py, qui est un module Python valide qui va contenir une classe Personne :

>>> from personne_pb2 import Personne
>>> p = Personne(nom="Gertrude", age=12)
>>> p.SerializeToString()
'\n\x08Gertrude\x10\x0c'

Il vous suffit d’envoyer ça par un socket, et de l’autre côté, une machine qui possède le même fichier .proto peut le lire et récupérer la donnée sous forme d’un objet Python, Java ou C++. Il a donc l’avantage d’un pickle, multi langages.

Parmi les bénéfices de protobuf, il y a que sa sortie est assez courte :

>>> json.dumps({"nom":"Gertrude", "age":12})
'{"nom": "Gertrude", "age": 12}'
>>> pickle.dumps({"nom":"Gertrude", "age":12})
'(dp0\nVnom\np1\nVGertrude\np2\nsVage\np3\nI12\ns.'

Ca fait moins de données à envoyer par le réseau.

Et en prime on a la validation des données :

>>> p.age = "12"
Traceback (most recent call last):
  File "<ipython-input-4-d897accc3848>", line 1, in <module>
    p.age = "12"
  File "/usr/lib/python2.7/dist-packages/google/protobuf/internal/python_message.py", line 435, in setter
    type_checker.CheckValue(new_value)
  File "/usr/lib/python2.7/dist-packages/google/protobuf/internal/type_checkers.py", line 104, in CheckValue
    raise TypeError(message)
TypeError: u'12' has type <type 'unicode'>, but expected one of: (<type 'int'>, <type 'long'>)

Du coup on peut utiliser protobuf en lieu et place d’un XML + DTD, en tout cas pour les cas simples.

Normalement, c’est aussi un format très rapide à parser.

Bref, Google a voulu le format pour les utilisations industrielles : c’est un peu chiant à mettre en place, mais c’est performant, robuste et ça marche avec les 3 langages qu’ils utilisent en interne.

Néanmoins ce n’est pas le seul à avoir pensé à ça : msgpack est une sorte de JSON binaire plus rapide à parser et qui prend moins de place. Il est assez utilisé avec les outils de file d’attente genre celery ou de communication type ZeroMq. Mais il perd un intérêt fort du JSON : sa transparence pour javascript, et n’a pas la vérification des données comme protobuf. BSON existe aussi dans le même genre, et sert de format de stockage pour mongodb, en supportant nativement des types avancées comme les dates.

Comme je vous le disais, des formats binaire, il y en a une bonne chiée.

La prochaine et dernière session, on se fera un petit tour des bases de données SQL et NoSQL.

flattr this!

Ca y est on est célèbres

dimanche 14 juillet 2013 à 20:18

Après l’apfy et reddit qui nous ont retiré de leur flux, et django-fr qui a refusé de nous y mettre du fait de nos articles NSFW, voici le grand Norton himself qui nous qui nous donne le traitement VIP.

Merci au lecteur qui nous a envoyé ça par email :

Capture d'écran du blocage de sam et max par Norton

Achievement unlocked

Heureusement, WOT nous aime toujours. Et puis, who wants MORE Norton ?

Évidement il me faut conclure sur un visuel qui convient au thème, et il m’est venu à l’esprit que vous avions négligé notre lectorat gay pendant trop longtemps.

Photo d'une partouze gay

C'est un peu un croisement entre une pub jean-paul gauthier et une station de métro du marais

flattr this!

Fichiers temporaires avec tempfile en Python

dimanche 14 juillet 2013 à 03:21

Il est de ces petits modules qui sont tout simples et bien pratiques. Et la lib standard de Python en regorge. Aujourd’hui (cette nuit, ce matin, choisissez votre référentiel, pour moi c’est le petit dej), on va voir le module tempfile.

Il permet de manipuler des fichiers et dossiers – qui après tout sont des fichiers – temporaires, c’est à dire jetable, en quelque sorte.

Donc :

>>> import tempfile

Dossier temporaire

La fonction la plus simple permet d’obtenir le dossier de l’OS dans lequel on met généralement les fichiers temporaires, comme par exemple C:\TEMP, C:\TMP, /tmp, /var/tmp, ou /usr/tmp selon la machine, l’OS, les variables d’environnement. Sur ma machine :

>>> tempfile.gettempdir()
'/tmp'

Si vous avez juste besoin d’un dossier temporaire dans lequel travailler :

>>> tempfile.mkdtemp()
'/tmp/tmps01VJw'

Il va être créé et sera accessible en lecture et écriture pour l’utilisateur qui l’a créé (permissions 700 sous Linux par exemple). Le chemin est retourné sous forme de string, à charge de votre programme de le supprimer, ou laisser l’OS s’en charger naturellement au prochain reboot.

Fichier temporaire

Il existe plusieurs manière de créer un fichier temporaire avec ce module, mais je vous invite à utiliser principalement :

>>> f = tempfile.NamedTemporaryFile()
>>> f
<open file '<fdopen>', mode 'w+b' at 0x1b7fc00>
>>> f.name
'/tmp/tmp0FxO_c'

C’est la méthode la plus simple et la plus passe-partout. Vous pouvez oublier mktemp(), mkstemp(), TemporaryFile(), etc. Le fichier est automatiquement supprimé quand vous appelez close() dessus, à moins de passer False via le paramètre delete.

Au passage vous pouvez influencer le nom du fichier résultant en passant prefix, suffix, et dir en arguments.

>>> tempfile.NamedTemporaryFile(prefix='~').name
u'/tmp/~XAoApc'
>>> tempfile.NamedTemporaryFile(suffix="tmp").name
u'/tmp/tmpbirE0Jtmp'
>>> tempfile.NamedTemporaryFile(suffix=".tmp").name
u'/tmp/tmpm4iHXE.tmp'
>>> tempfile.NamedTemporaryFile(dir="/home/sam/.tmp").name
u'/home/sam/.tmp/tmpHfwhQI'
>>> tempfile.NamedTemporaryFile(dir="/home/sam/.tmp").name

mode et bufsize sont aussi disponibles, et seront relayés à file() sous le capot.

>>> tempfile.
tempfile.NamedTemporaryFile    tempfile.TMP_MAX               tempfile.gettempdir            tempfile.mkdtemp               tempfile.mktemp                tempfile.template
tempfile.SpooledTemporaryFile  tempfile.TemporaryFile         tempfile.gettempprefix         tempfile.mkstemp               tempfile.tempdir
>>> tempfile.get
tempfile.gettempdir     tempfile.gettempprefix
>>> tempfile.gettempdir()
'/tmp'

Une dernière astuce qui ne m’a jamais servi : tempfile.SpooledTemporaryFile(max_size=X). Similaire à NamedTemporaryFile, mais le fichier est uniquement en mémoire, et sera transformé en un fichier sur le disque si sa taille dépasse X octets. StringIO est utilisé pour la partie en RAM.

Le fait de demander son file descriptor (l’entier qui représente le fichier pour le programme) le fait aussi passer en mode “sur le disque” :

>>> a = tempfile.SpooledTemporaryFile(max_size=10000)
>>> a.name
Traceback (most recent call last):
  File "<ipython-input-43-c0a6f6c60584>", line 1, in <module>
    a.name
  File "/usr/lib/python2.7/tempfile.py", line 569, in name
    return self._file.name
AttributeError: 'cStringIO.StringO' object has no attribute 'name'
 
>>> a.fileno()
9
>>> a.name
'<fdopen>'

flattr this!

Créer un décorateur à la volée

vendredi 12 juillet 2013 à 07:39

Sur les décorateurs, normalement, vous avez tout ce qu’il faut pour être au point.

Néanmoins en informatique la moitié de la connaissance, c’est l’usage, pas la fonctionnalité. Car il y beaucoup d’usages auxquels on ne pense pas.

Particulièrement, je vous avais déjà expliqué que les fonctions étaient des objets comme les autres en Python, et qu’on pouvait donc les créer à la volée, les retourner, les passer en paramètre, et même leur coller des attributs.

Or les décorateurs ne sont jamais que des fonctions.

Maintenant, souvenez vous, le décorateurs property permet de faire ceci :

class Mamouth(object):
 
    _valeur = "3 calots"
 
    @property
    def valeur(self):
        return self._valeur.upper()
 
    @valeur.setter
    def valeur(self, valeur):
        self._valeur = valeur.strip()
 
>>> bille = Mamouth()
>>> bille.valeur
u'3 CALOTS'
>>> bille.valeur = "une pépite           "
>>> bille.valeur
>>> print(bille.valeur)
UNE PÉPITE

La syntaxe qui doit attirer votre attention est @valeur.setter. En effet, d’où vient ce décorateur ?

On comprend mieux ce qui s’est passé avec ce test :

>>> Mamouth.valeur.setter
<built-in method setter of property object at 0x1a1baf8>

setter est tout simplement un attribut de la méthode valeur. Par ailleurs, c’est une fonction et un décorateur.

Pourquoi faire cela ? Et bien tout simplement parce que cela permet d’attacher une fonction qui ne sert que dans un cas (ici ça ne sert qu’à créer le setter de la propriété valeur) à son contexte.

Bien entendu vous pouvez faire ça vous même, il suffit de le vouloir très fort et de croire en le pouvoir de l’amour.

Par exemple, imaginez un décorateur qui permet d’attacher un comportement de sérialisation à une fonction. On ne veut pas modifier la fonction, mais on veut qu’elle puisse automatiquement, pour quelques caractères de plus, pouvoir aussi retourner du JSON ou du pickle.

import json
import pickle
 
def serializable(func):
 
    # Contrairement à la plupart des décorateurs, on ne va pas retourner
    # un wrapper, mais bien la fonction originale. Simplement on lui aura ajouté
    # des attributs
 
    func.as_json = lambda *a, **k: json.dumps(func(*a, **k))
    func.as_pickle = lambda *a, **k: pickle.dumps(func(*a, **k))
 
    return func

Et ça s’utilise ainsi :

import locale
 
from calendar import TimeEncoding, day_name, day_abbr
 
# obtenir les noms de jours localisés est complètement rocambolesque en python
def get_day_name(day_number, locale, short=False):
    """
        Retourne le nom d'un jour dans la locale sélectionnée.
 
        Exemple :
 
        >>> get_day_name(0,  ('fr_FR', 'UTF-8'))
        'lundi'
    """
    with TimeEncoding(locale) as encoding:
        s = day_abbr[day_number] if short else day_name[day_number]
        return s.decode(encoding) if encoding is not None else s
 
@serializable
def get_days_names(locale=locale.getdefaultlocale(), short=False):
    """
        Un dictionnaire contenant un mapping entre les numéros des jours
        de semaine et leurs noms selon la locale donnée.
    """
 
    return {i: get_day_name(i, locale) for i in xrange(7)}

En usage ordinaire, la fonction retourne bien ce qui est prévu :

>>> get_days_names()
{0: 'lundi', 1: 'mardi', 2: 'mercredi', 3: 'jeudi', 4: 'vendredi', 5: 'samedi', 6: 'dimanche'}
>>> get_days_names(locale=('en_US', 'UTF-8'))
{0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday', 4: 'Friday', 5: 'Saturday', 6: 'Sunday'}

Mais on peut choisir le format à la sortie :

>>> get_days_names.as_json()
'{"0": "lundi", "1": "mardi", "2": "mercredi", "3": "jeudi", "4": "vendredi", "5": "samedi", "6": "dimanche"}'
>>> get_days_names.as_pickle(locale=('en_US', 'UTF-8'))
"(dp0\nI0\nS'Monday'\np1\nsI1\nS'Tuesday'\np2\nsI2\nS'Wednesday'\np3\nsI3\nS'Thursday'\np4\nsI4\nS'Friday'\np5\nsI5\nS'Saturday'\np6\nsI6\nS'Sunday'\np7\ns."

Ici, on a attacher une fonction à une autre fonction, en mettant la deuxième dans un attribut de la première.

Comme les décorateurs sont des fonctions, rien ne vous empêche de faire pareil avec un décorateur, et c’est de cette manière que @property attache un décorateur setter à chaque méthode.



Télécharger le code des articles

flattr this!