Posts Tagged ‘Python’

Arrêtez d’écrire des classes !

Monday, December 30th, 2013

En créant les nouvelles fonctionnalités ASCII art de txt2tags, j’ai été amené à développer un certain nombre de fonctions spécifiques que j’ai donc logiquement fini par extraire dans une librairie autonome nommée aa.py. Pour des raisons historiques, d’habitude des utilisateurs et de facilité de diffusion, Aurélio Jargas – le BDFL de txt2tags – m’a demandé de réintégrer ces fonctions dans le fichier principal, ce à quoi il a ajouté :

Aurélio: move them back to global functions or creating a aa class in txt2tags?
option 2 would be cool

J’ai réfléchi, puis j’ai réintégré mes fonctions, telles quelles, sans créer de classe aa. Créer une classe dans ce cas ne me semblait pas adapté, complexifiant le code sans raison théorique valable ni contrepartie pratique positive.

Peu de temps après, je suis tombé sur une présentation intitulée Stop Writing Classes faite par le core developer Python Jack Diederich lors du PyCon 2012. J’y ai retrouvé beaucoup de choses que je pensais et d’autres que je n’avais pas encore clairement formalisées. Elle est particulièrement intéressante car elle utilise de vrais exemples de code, dont une horreur produite par des ingénieurs de chez Google !

On pourrait résumer cette réflexion avec les deux principes les plus importants de la programmation selon moi : KISS (Keep It Simple, Stupid) et DRY (Don’t Repeat Yourself). On peut remarquer que ce qui est pertinemment décrit comme une mauvaise utilisation des classes, correspond globalement à un style de programmation à la Java. Pour ceux qui ne comprendraient pas l’anglais et pour les gens pressés, voici donc les principales recommandations à retenir :

  • Diminuer le nombre de classes en refactorisant le code ;
  • Ne pas écrire de classes vides, qui pourraient être utiles plus tard, en utilisant pass, les écrire plus tard si besoin ;
  • Si une classe n’a que deux méthodes, et que l’une d’elle est __init__, c’est que ce ne devrait pas être une classe ;
  • Quand un dictionnaire suffit, ne pas le camoufler dans une classe ;
  • Ne pas créer de nouvelles exceptions, utiliser celles de la librairie standard, qui sont connues et comprises de tous ;
  • Les espaces de noms existent pour éviter les collisions, pas pour faire de la taxinomie, il faut donc qu’ils restent le plus plat possible.

Je finis en donnant la parole à la défense, avec cet article technique d’Armin Ronacher intitulé Start Writing More Classes.

La compréhension de dictionnaire en Python

Saturday, October 5th, 2013

La compréhension de liste est une syntaxe moderne remplaçant les classiques fonctions map() et filter() de la programmation fonctionnelle. Ce sucre syntaxique est devenu un standard de la programmation moderne présent dans quasiment tous les langages.

La PEP 202, en intégrant cette fonctionnalité dans Python 2.0, n’était donc pas d’une originalité folle. Mais les développeurs de Python ne se sont pas arrêtés en si bon chemin, et ils ont eu l’intelligence de pousser le concept un peu plus loin. Ainsi, ils ont cherché à étendre la syntaxe de compréhension aux autres types conteneurs que les listes, c’est-à-dire en premier lieu aux dictionnaires.

La PEP 274 visait donc a intégrer la compréhension de dictionnaire à Python 2.3, mais elle a été abandonnée car considérée comme inutile au vu de l’utilisation conjointe des expressions de générateur et du constructeur dict().

Cependant, l’intérêt pour ce sucre syntaxique est resté présent, et la compréhension de dictionnaire a finalement été intégrée au langage à l’occasion de la grande réforme du passage à Python 3.0, et backportée dans Python 2.7.

On obtient ainsi plus de lisibilité :

>>> dict((i,i*2) for i in range(5))
{0: 0, 1: 2, 2: 4, 3: 6, 4: 8}
>>> {i: i*2 for i in range(5)}
{0: 0, 1: 2, 2: 4, 3: 6, 4: 8}

et de flexibilité :

>>> k_and_v = [[1, 'a'], [2, 'b']]
>>> dict(k_and_v)
{1: 'a', 2: 'b'}
>>> {k: v for k, v in k_and_v}
{1: 'a', 2: 'b'}
>>> {k+1: v*2 for k, v in k_and_v}
{2: 'aa', 3: 'bb'}

Et puisque Python 3.0 a doté les sets d’une nouvelle syntaxe, il en a aussi profité pour les doter de la compréhension :

>>> [i%2 for i in range(8)] # liste
[0, 1, 0, 1, 0, 1, 0, 1]
>>> {i%2 for i in range(8)} # set
{0, 1}

Il n’est pas étonnant que la syntaxe de compréhension de liste se soit diffusée dans tous les langages de programmation modernes. Il n’est pas étonnant non plus que Python, un langage doté d’une philosophie très sensible à la lisibilité, soit allé plus loin en la généralisant à tous ses types conteneurs, en faisant un élément central du langage.

La syntaxe des sets en Python

Thursday, March 14th, 2013

Pour comprendre la génèse de la syntaxe des sets, il faut étudier celle des types conteneurs historiques de Python qui sont :

  • les listes, délimitées par les crochets [ et ]
  • les tuples, délimités par les parenthèses ( et )
  • les dictionnaires, délimités par les accolades { et }

Ces types conteneurs sont donc dotés d’une syntaxe légère et facilement utilisable :

>>> l = [1, 2, 3]
>>> l
[1, 2, 3]
>>> t = (1, 2, 3)
>>> t
(1, 2, 3)
>>> d = {1: '1', 2: '2', 3: '3'}
>>> d
{1: '1', 2: '2', 3: '3'}

Les tuples, qui sont en fait des listes immutables et que l’on ne peut donc pas modifier, sont souvent oubliés car assez peu utilisés, en tout cas de manière consciente. En effet, une simple énumération sans syntaxe spécifique est en fait un tuple :

>>> e = 1, 2, 3
>>> e
(1, 2, 3)

Si l’on peut donc dire que globalement, les listes et les dictionnaires répondent à la grande majorité des besoins des programmeurs, un quatrième type conteneur s’est lentement mais sûrement fait une place au soleil des pythonistes : les sets.

Un set est un ensemble, c’est-à-dire une collection non ordonnée d’éléments uniques, ce qui se révèle très pratique dans beaucoup d’usages courants. Ce nouveau type conteneur étant fourni en deux saveurs, l’une mutable (comme les listes), et l’autre immutable (comme les tuples).

D’abord introduits dans Python 2.3 sous la forme d’un nouveau module sets ajouté à la bibliothèque standard. Il est ensuite devenu un type built-in dans Python 2.4, ce qui représentait une amélioration syntaxique non négligeable. Plus besoin d’écrire :

>>> import sets
>>> s_mutable = sets.Set([1, 2, 3])
>>> s_immutable = sets.ImmutableSet([1, 2, 3])

On pouvait dorénavant se contenter de :

>>> s_mutable = set([1,2,3])
>>> s_immutable = frozenset([1,2,3])

Et de bénéficier en plus d’une implémentation du type set en C et non plus en Python, avec la belle augmentation de performance qui va avec.

L’intégration des sets mutables (les plus utiles) dans le core du langage Python, au même niveau que les tuples, les listes et les dictionnaires, se heurtait encore à une limitation syntaxique : il fallait écrire set([1, 2, 3]). En effet, il n’y a que trois signes de ponctuation ASCII 7 bits fonctionnant par paire et facilement accessibles sur un clavier, les parenthèse, les crochets et les accolades, qui comme on l’a vu sont déjà utilisés respectivement par les tuples, les listes et les dictionnaires.

Mais que pouvait-on alors faire pour mieux intégrer syntaxiquement les sets à Python ? C’est dans Python 3.0 que la solution a été trouvée : si les tuples et les listes sont des énumérations que l’on ne pourrait distinguer des sets, les dictionnaires sont eux bien différents, et l’on peut donc utiliser les accolades { et } pour une énumération sans risque de confusion :

>>> s = {1, 2, 3}
>>> s
{1, 2, 3}

Il reste cependant une petite exception qui rend cette solution syntaxique imparfaite, et potentiellement génératrice d’erreurs ou d’incompréhensions, c’est le cas de l’ensemble vide. En effet, {} ne peut pas représenter à la fois le dictionnaire vide et le set vide :

>>> s = {}
>>> s
{}
>>> isinstance(s, set)
False
>>> isinstance(s, dict)
True

De manière logique et rétrocompatible, {} représente donc toujours le dictionnaire vide, et c’est set() qui permet de créer un set vide :

>>> s = set()
>>> s
set()
>>> s.add(1)
>>> s
{1}

Enfin, ce sucre syntaxique de Python 3 a été backporté dans Python 2.7.

 

Complément à l’article de Carl sur SQLite et Python

Tuesday, February 14th, 2012

Carl vient de publier, dans le dernier Linux Pratique Hors-Série sur Python, un article sur le module sqlite3, disponible dans la bibliothèque standard depuis Python 2.5, et qui permet d’utiliser facilement SQLite.

C’est un article très didactique, qui tombait à point nommé pour moi puisque j’ai récemment eu plusieurs idées d’intégration de SQLite dans txt2tags. Je suis parti totalement de zéro, puisque j’ai toujours fui aussi bien le langage SQL que la configuration des systèmes de gestion de bases de données, et j’ai quelques remarques complémentaires à faire qui pourront aider les personnes dans le même situation que moi.

Sur le shell sqlite3

Le shell sqlite3 est très pratique pour interagir avec les bases de données SQLite, et en particulier vérifier celles générées par nos programmes Python. Cependant, l’affichage standard est un peu spartiate :

sqlite> select * from tab;
1|gpl|3|premier test
2|linux|5|deuze

Pour une meilleur visualisation, on peut ajouter les noms des champs :

sqlite> .header on
sqlite> select * from tab;
id|title|length|comment
1|gpl|3|premier test
2|linux|5|deuze

Afficher en colonnes :

sqlite> .mode column
sqlite> select * from tab;
1           gpl         3           premier test
2           linux       5           deuze

Et même les deux à la fois :

sqlite> select * from tab;
id          title       length      comment
----------  ----------  ----------  ------------
1           gpl         3           premier test
2           linux       5           deuze

Sur la table sqlite_master

Dans l’article il est précisé comment obtenir la liste des tables d’une base de données grâce à une commande du shell sqlite3 :

sqlite> .tables
tab

Mais pas comment obtenir le même résultat d’une requête SQL que vous pouvez effectuer en Python, et qui utilise la table sqlite_master automatiquement créée par SQLite :

sqlite> select name from sqlite_master;
tab

Sur la méthode iterdump()

Deux remarques :

  1. Elle n’est disponible que depuis Python 2.6
  2. C’est selon Gerhard Hæring lui-même, l’auteur du module sqlite3, la seule méthode pour dupliquer une base en mémoire vers une base sur disque (oui, c’est vraiment très moche qu’il n’y ai pas de manière plus élégante de le faire, mais c’est comme ça !)

Sur les ? et les chaînes de caractères

Pour construire dynamiquement le contenu des requêtes SQL, Carl utilise sans le justifier des ? et un tuple de valeurs comme second argument de la méthode execute(). Il est en fait très important de suivre cette manière de faire, car si vous utilisez les opérations classiques de concaténation des chaîne de caractères, vous vous exposez à des risques d’attaque par injection SQL !

L’autre article de Carl sur l’utilisation du module smtplib est aussi très intéressant, les autres articles du Hors-Série formant en fait un gros tutoriel d’initiation au langage Python.

Django 1.4 passe à HTML5 ! et autres nouveautés

Tuesday, January 3rd, 2012

Le 22 décembre dernier est sorti Django 1.4 alpha 1. Comme on peu l’imaginer pour un projet d’une telle ampleur, cette nouvelle version apporte beaucoup de nouveautés. Je me contenterai d’en détailler rapidement cinq qui me semblent particulièrement intéressantes :

    1. Le passage au Doctype HTML5, tous les templates fournis avec Django, en particulier ceux de l’interface d’administration, utilisant donc désormais <!DOCTYPE html>. C’est un choix logique, qui permet d’utiliser toutes les nouvelles fonctionnalités de HTML5 sans plus se soucier de devoir corriger le Doctype.
    2. L’abandon du support de Python 2.4, datant de 2004, mais pas le passage à Python 3. Django supporte et est testé sur Python 2.5, 2.6 et 2.7.
    3. Le nouveau framework de test LiveServerTestCase, compatible avec Selenium et Windmill, pour tester l’interface de votre application web côté client (dans le navigateur web).
    4. La nouvelle option --template pour les commandes startapp et startproject, permettant de leur spécifier aisément un template personnalisé.
    5. La nouvelle clause elif pour la balise if. Elle permettra de ne plus avoir à imbriquer plusieurs if then else et autant d’indentations ou à se définir une balise personnalisée. Alors oui j’ose le dire : c’est pas trop tôt !

On voit donc qu’encore une fois Django continue d’avancer dans la bonne direction, bonifiant sans cesse une base déjà excellente. La seule chose que je regrette encore et toujours, c’est la défiance manifeste de la core team envers l’intégration de django-nonrel… peut-être pour Django 1.5 ?

La compréhension de liste en Python, une syntaxe moderne pour map() et filter()

Monday, December 5th, 2011

La compréhension de liste est un syntactic sugar pour les fonctions classiques de la programmation fonctionnelle que sont map() et filter(). Disponible depuis Python 2.0, la compréhension de liste devrait à terme amener à la disparition des fonctions map() et filter() du langage Python, ce que Guido van Rossum avait déjà envisagé pour Python 3.

Ainsi, si vous avez du “vieux code” utilisant ces fonctions, je vous encourage à le porter vers cette nouvelle syntaxe beaucoup plus lisible et plus pythonique. J’espère que cet article pourra aider certains à éviter les pièges qui pourraient apparaître devant eux lors de cette démarche.

map()
Commençons d’abord par la fonction map(), dont la syntaxe est map(fonction, liste). Prenons un exemple très simple, avec une fonction anonyme lambda qui à tout x élément de la liste des chiffres de 0 à 9 associe 2 fois x.

>>> map(lambda x: 2*x, range(10))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

On obtient l’équivalent de la fonction map() avec une compréhension de liste de la forme [fonction(x) for x in liste], ce qui donne pour notre exemple :

>>> [2*x for x in range(10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

filter()
La syntaxe de la fonction filter() est filter(fonction, liste). Prenons ici aussi un exemple très simple, avec une fonction anonyme lambda qui n’est Vrai pour chaque x élément de la liste des chiffres de 0 à 9 que s’il est strictement supérieur à 5.

>>> filter(lambda x: x>5, range(10))
[6, 7, 8, 9]

On obtient l’équivalent de la fonction filter() avec une compréhension de liste de la forme [x for x in liste if fonction(x)], et donc notre exemple devient :

>>> [x for x in range(10) if x>5]
[6, 7, 8, 9]

Un des grands intérêts des fonctions est de pouvoir être composées. Cependant, cette opération de composition n’est pas anodine, et il faut bien envisager les différents cas pour ne pas commettre d’erreur.

map o filter()
La composition notée map o filter() revient à map(filter()), c’est-à-dire à appliquer d’abord la fonction filter(), puis la fonction map(). Si nous combinons nos deux premiers exemples, nous obtenons :

>>> map(lambda x: 2*x, filter(lambda x: x>5, range(10)))
[12, 14, 16, 18]


On obtient l’équivalent de la composition de fonction map o filter() avec une compréhension de liste de la forme [fonction_map(x) for x in liste if fonction_filter(x)], ce qui donne ici :

>>> [2*x for x in range(10) if x>5]
[12, 14, 16, 18]

La composition de fonction map o filter() ne pose pas de problème.

On remarque que plus l’on compose des fonctions map() et filter(), plus l’avantage syntaxique de lisibilité et de compréhensibilité des compréhensions de liste augmente.

filter o map()
Mais qu’en est-il de la composition notée filter o map(), qui revient à filter(map()), c’est-à-dire à appliquer d’abord la fonction map(), puis la fonction filter(). Si nous combinons ici aussi nos deux premiers exemples, nous obtenons :

>>> filter(lambda x: x>5, map(lambda x: 2*x, range(10)))
[6, 8, 10, 12, 14, 16, 18]

Le résultat retourné est différent de celui obtenu à l’exemple précédent avec une compréhension de liste de la forme [fonction_map(x) for x in liste if fonction_filter(x)]. En fait, la bonne compréhension de liste est :

>>> [2*x for x in range(10) if 2*x>5]
[6, 8, 10, 12, 14, 16, 18]

Nous voyons que la composition de fonction filter o map() pose des problèmes, et qu’il faudra y porter une attention particulière en cas de conversion de vieux code fonctionnel en compréhensions de liste.

filter o filter()
La composition notée filter o filter() revient à filter(filter()), c’est-à-dire à appliquer deux fois de suite la fonction filter(). Nous allons donc rajouter une deuxième condition à notre premier exemple de filter(), qui sera que chaque élément x devra en plus être strictement inférieur à 8.

>>> filter(lambda x: x<8, filter(lambda x: x>5, range(10)))
[6, 7]

Le même résultat est obtenu en inversant l’ordre des deux conditions :

>>> filter(lambda x: x>5, filter(lambda x: x<8, range(10)))
[6, 7]

La composition filter o filter() est commutative, et peut en fait facilement se factoriser sous la forme :

>>> filter(lambda x: x>5 and x<8, range(10))
[6, 7]

On obtient l’équivalent de la composition de fonction filter o filter() avec une compréhension de liste de la forme [x for x in liste if fonction_filter_1(x) and fonction_filter_2(x)], ce qui donne ici :

>>> [x for x in range(10) if x>5 and x<8]
[6, 7]

La composition de fonction filter o filter() ne pose pas de problème.

map o map()
Mais qu’en est-il de la composition notée map o map(), qui revient à map(map()), c’est-à-dire à appliquer deux fois de suite la fonction map() ? Rajoutons une deuxième fonction à notre premier exemple de map(), qui à chaque x associera x plus 1.

>>> map(lambda x: x+1, map(lambda x: 2*x, range(10)))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

et

>>> map(lambda x: 2*x, map(lambda x: x+1, range(10)))
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

ne donnent pas les mêmes résultats. La composition map o map() n’est pas commutative. On obtient l’équivalent de la composition de fonction map o map() avec une compréhension de liste de la forme [fonction_map_2(x) for x in [fonction_map_1(x) for x in liste]], ce qui donne ici :

>>> [x+1 for x in [2*x for x in range(10)]]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

et

>>> [2*x for x in [x+1 for x in range(10)]]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Il est possible de factoriser les deux fonctions map() comme nous l’avons fait pour les fonctions filter(), mais il faut faire très attention à l’ordre de composition des fonctions. Dans le premier cas on a (2*x)+1 = 2*x+1 qui donne :

>>> [2*x+1 for x in range(10)]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

et dans le deuxième cas on obtient 2*(x+1) = 2*x+2 qui produit :

>>> [2*x+2 for x in range(10)]
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Nous voyons que la composition de fonction map o map() pose des problèmes.

Pour ne pas tomber dans des pièges, il suffit de retenir que toutes les compositions de fonctions qui commencent par une fonction map() sont dangereuses, alors que celles commençant par une fonction filter() sont sans danger.

Pour mon plus grand bonheur, la syntaxe de compréhension de liste rend particulièrement pythonique et centrale au langage Python une partie de la sémantique de la programmation fonctionnelle qui y était jusqu’à présent mal intégrée et mal aimée. Encore une fois, et même si certains de mes professeurs d’informatique ne l’ont jamais accepté, sans une bonne syntaxe, une sémantique géniale n’est rien.

L’impact de Django

Thursday, September 1st, 2011

Armin Ronacher, membre de la Pocco Team, est le principal développeur de nombreux projets libres comme le langage de template Jinja et le microframework Flask, qui sont des programmes Python très populaires et d’excellente qualité.

Le 8 juin, lors du DjangoCon Europe 2011, il a fait une présentation intitulée The Impact of Django ayant un contenu similaire en bien des points à mon article du 6 juin intitulé Et le meilleur framework web Python est… Django !. Vous pouvez voir la conférence en ligne et en consulter ou télécharger les slides.

Bien sûr, il est assez plaisant de constater qu’un développeur, qui n’est pas loin d’être le meilleur au monde sur le sujet, fait une analyse très proche de la mienne en particulier en prenant le contre-exemple de TurboGears et en montrant l’influence du langage de template de Django. Cependant, il ajoute deux éléments  d’analyse, l’un social et l’autre juridique.

Premièrement, Armin pense que, d’une manière assez similaire à Ruby on Rails pour Ruby, Django est actuellement la locomotive du langage Python :

Django is the reason Python is getting that much attention lately.

Ainsi, environ 1 200 des 15 000 paquets disponibles sur PyPi contiendraient “Django” dans leur nom, de même que 10 000 des 77 000 dépôts sur GitHub et 3 000 dépôts sur Bitbucket. Si Python est beaucoup plus utilisé que Ruby en dehors du web, l’importance de ce dernier est telle qu’il est logique que le logiciel qui y est leader soit aussi le leader du langage.

Deuxièmement, Armin lie le succès de la licence BSD dans la communauté Python avec celui de Django et de son écosystème. Ansi 700 des 2 500 paquets sous licence BSD sur PyPI sont classés Django. Et surtout la licence BSD semble être utilisée par des projets assez récents, quand les plus anciens privilégiaient la licence GNU GPL.

Le choix de la licence est quelque chose de fondamental pour un logiciel libre, et il faut réfléchir aux implications réelles qui en découlent. En pratique, dans le contexte du serveur web qui est celui de Django, la licence GNU GPL n’est pas spécialement plus contraignante qu’une licence BSD, n’obligeant pas à redistribuer le code source modifié, et choisir l’une plutôt que l’autre n’a donc pas vraiment de signification pertinente.

Pour ce type de logiciel, si l’on veut voir s’appliquer la puissance du copyleft, il faut utiliser la licence GNU Affero GPL. À titre personnel, c’est la licence que je conseille, mais si l’on veut une licence libérale, alors je trouve effectivement plus clair de choisir une licence BSD qu’une licence GNU GPL vidée de sa substance par le contexte du logiciel serveur.

Comparaison des langages de balisage (markup) léger (lightweight) : Txt2tags, Pandoc, Docutils, AsciiDoc, Deplate, Stx2any, AFT, Markdown et Textile

Sunday, August 14th, 2011

La bureautique est la principale utilisation de l’informatique depuis sa création. Pourtant, les outils majoritairement utilisés dans ce domaine, les logiciels de traitement de texte WYSIWYG comme LibreOffice et OpenOffice, laissent la majorité des informaticiens et des ergonomes très dubitatifs, voire totalement désespérés.

Ces logiciels ont en effet un nombre de défauts très important : ils font se concentrer sur la forme et non sur le fond, leur résultat final ne correspond souvent pas à ce qui est affiché, ils sont incompatibles entre eux, ce sont d’énormes usines à gaz inutilisables sur de vieilles machines, ils ne fonctionnent qu’en mode graphique, etc. La seule manière rationnelle, efficace et interopérable de travailler sur un ordinateur est d’utiliser de simples fichiers textes, tous les documents étant donc modifiable dans n’importe quel éditeur de texte.

Il a donc fallu penser à une manière de donner ces instructions de mise en forme au sein du fichier texte lui-même, et c’est ainsi que sont apparus les langages de balisage (markup), dont les plus connus sont HTML (inventé en 1991 par Tim Berners-Lee) et LaTeX (créé en 1985, et basé sur TeX, inventé par le grand Donald Knuth en 1977), et dont la première grande figure fut Roff, un programme Unix historique développé à partir de 1961, et dont la version GNU, Groff, est installée par défaut sur toutes les distributions Linux, puisqu’on l’utilise encore pour les pages de man des logiciels.

Pour mieux visualiser, prenons comme exemple la création d’une nouvelle section d’un document en man :

.SH Nouvelle section en man

en HTML :

<h1>Nouvelle section en HTML</h1>

et en LaTeX :

\section{Nouvelle section en LaTeX}

Ces langages représentent une nette amélioration, mais ont tous un gros problème : ils sont gênants ! On ne retrouve plus aussi facilement son contenu au milieu de toutes ces balises supplémentaires, sans parler du fait que les syntaxes complexes ouvrent la voie à de nombreuses erreurs de compilation.

C’est en 1995 que l’on trouva la solution de ce problème, avec la création du premier langage Wiki, dont le but principal était de permettre l’édition facile de pages web par tout un chacun, et dont l’utilisateur actuel le plus célèbre est l’encyclopédie libre Wikipédia. S’il y a presque autant de syntaxes différentes que de logiciels Wiki, elles ont toutes la caractéristique d’utiliser des caractères textuels simples et intuitifs pour donner les indications de formatage du texte.

Toujours le même exemple, une nouvelle section en MediaWiki :

= Nouvelle section Wiki =

et une en Setext :

Nouvelle section Setext
=======================

Mais pourquoi limiter ces langages de balisage léger à la seule génération de HTML ? Pourquoi ne pas utiliser la même syntaxe pour différentes cibles (appelées backends, targets ou writers selon les logiciels), de manière à obtenir aussi bien une page web en HTML, qu’un document en LaTeX pour l’impression, ou qu’une page de man pour un logiciel ? Ce sont les logiciels qui poursuivent ce but qui m’intéressent, ils constituent pour moi l’avenir de la bureautique informatique, et j’ai été amené à les comparer pour en choisir un dans lequel m’investir comme développeur.

Voici un tableau comparatif des meilleurs logiciels libres existants, avec comme information supplémentaire l’existence d’une cible texte brut, puisque je souhaitais me baser sur ce code pour programmer certaines de mes idées ASCII art.

Les logiciels complets MAJ

NomLangage de programmationPopularité du projeti18nCible texte brutLicence
Txt2tagsPythonmoyenne + RedNotebookOui + docOuiGNU GPLv2 ou suivante
DocutilsPythonforte + SphinxNonNonDomaine public
AsciiDocPythonforteNonDump de w3m ou de LynxGNU GPLv2 ou suivante
DeplateRubyfaibleNonOuiGNU GPLv2 ou suivante
PandocHaskellmoyenneNonOuiGNU GPLv2 ou suivante
Stx2anySed et m4faibleNonDump de w3mLicence copyleftée personnalisée
AFTPerlfaibleNonNonClarified Artistic License
LunamarkLuafaibleNonNonMIT
JeromeErlangfaibleNonNonMIT
DoxiaJavaforteNonNonApache License 2.0
XWiki RenderingJavafaibleNonOuiGNU LGPLv2.1

Exceptés Docutils et AFT (Almost Free Text), tous les logiciels semblaient au premier abord proposer une cible texte. Mais en fait c’est un peu un trompe l’œil, car deux d’entre eux, AsciiDoc et Stx2any, se contentent de dumper un navigateur web en mode texte (w3m ou Lynx) du résultat généré par leur cible HTML. Il n’y avait donc pas dans ces cas de base de code que je pouvais espérer améliorer.

J’ai mis dans le comparatif stx2any et AFT codés respectivement en m4 et en Perl, de manière à proposer un large éventail de langages de programmation, mais ces logiciels sont à la fois moins utilisés et dotés de moins de fonctionnalités que les autres. En étudiant les différents logiciels disponibles, je me suis rendu compte que, par chance pour moi, beaucoup étaient codés en Python (3 sur 11 au total, mais surtout 3 sur les 5 vraiment intéressants). Ce n’est d’ailleurs pas la première fois que je constate, en cherchant à sélectionner des logiciels libres, que ceux codés en python sont à la fois les plus nombreux et les meilleurs.

Les cinq logiciels restants sont vraiment excellents, et constituent tous un bon choix. Trois d’entre eux, Docutils, Deplate et Pandoc, ont un design évolué, avec une machine à états finis pour laquelle on peut écrire de nouveaux readers et writers de manière parfaitement propre. Cependant, malgré leurs grandes qualités, Deplate est un projet trop confidentiel (ainsi il n’est incompréhensiblement pas présent parmi les pourtant si nombreux paquets Debian), et je ne me sentais pas à la hauteur pour m’investir dans un projet comme Pandoc, totalement écrit en Haskell, qui est un langage de programmation complexe que j’aimerais beaucoup utiliser, mais où ma compétence est encore trop limitée.

Il fallait donc que j’approfondisse ma réflexion sur les logiciels en Python :

Choix en Python

NomCibles principalesCible texte brut
Txt2tagsHTML, Latex et syntaxes WikiOui
DocutilsHTML et LatexNon
AsciiDocHTML et DocBookDump
MarkdownHTMLNon
TextileHTMLNon

J’ai rajouté dans ce comparatif Markdown et Textile, puisqu’ils ont chacun une implémentation en Python, mais ne générant que du HTML, ils ne m’intéressaient pas vraiment. AsciiDoc et Txt2tags ont un peu la même architecture, avec un gros fichier principal faisant tout le travail, que l’on peut configurer, respectivement avec un fichier .conf et deux dictionnaires Python (un pour les Tags et l’autre pour les Rules), pour créer de nouvelles cibles. AsciiDoc et Txt2tags sont donc plus aisés à prendre en main et à modifier rapidement que Docutils, qui est une très belle et très bien architecturée machine à états objet, mais aussi plus difficile à appréhender.

De plus, comme je désapprouvais totalement la politique de licence domaine publique de Docutils, il ne me restait plus qu’à faire mon choix entre Txt2tags et AsciiDoc. C’est principalement l’orientation très DocBook (un format ne m’intéressant personnellement pas du tout) d’AsciiDoc, et d’autres détails, comme la localisation en de nombreuses langues de Txt2tags et sa plus grande simplicité, qui m’ont finalement fait choisir Txt2tags.

Ce choix est confirmé par une étude plus avancée des différentes syntaxes. Ainsi alors que la syntaxe reST de Docutils ne dispose que de :

*italique* et **gras**

Txt2tags est beaucoup plus riche :

//italique// **gras** __souligné__ et --barré--

Le codage visuel est bien meilleur, et le compréhension instantanée avec la syntaxe de Txt2tags, puisque les slashs donnent l’impression penchée de l’italique, les étoiles imitent la surcharge du gras, les underscores donnent l’impression de soulignement, et les moins apparaissent comme une barre. De plus, l’utilisation généralisée des caractères de balisage en doubles, permet de lever à peu de frais un maximum d’ambiguïtés syntaxiques.

Par exemple une phrase aussi simple que celle contenue dans le fichier d’exemple de Txt2tags :

We use double *, /, - and _ to represent **bold**, //italic//, --strike-- and __underline__.

et qui ne pose aucun problème :

est en fait déjà trop ambigüe en reST, puisque :

We use double *, /, - and _ to represent **bold**, *italic*, strike and underline.

donnera :

De manière générale, la syntaxe reST est souvent trop lourde. Par exemple dans le cas de l’insertion d’une image servant de lien :

.. image:: picture.png
:target: http://fgallaire.flext.net

que l’on peut comparer avec le beaucoup plus simple Txt2tags :

[[picture.png] http://fgallaire.flext.net]

On remarque bien sûr ici que la syntaxe reST offre sûrement plein d’autres options que la simple :target:, et qu’elle est donc plus puissante que celle de Txt2tags. Cependant, mon avis est que la pénalité de lourdeur est toujours présente, alors qu’elle n’est utile que dans peut-être 1% des cas.

Voici maintenant un comparatif des logiciels disponibles par syntaxe :

Classement par syntaxe

UsageTxt2tagsAsciiDocreStructuredTextMarkdown
BureautiqueTxt2tagsAsciiDocDocutils
Pandoc
Pandoc
Web serveurTxt2tags (Python)
PHP
AsciiDoc (Python)Docutils (Python)Perl
Python
Ruby
PHP
Erlang
Java
Web clientmarked
Showdown
markdown-js

Leur implémentation en Python permet à Txt2tags, reST (par Docutils) et AsciiDoc d’être utilisables à la fois comme logiciels de bureautique multiplateforme (Linux, Mac OS X, Windows et *BSD) et pour le web côté serveur. Depuis 2012, une implémentation de txt2tags en PHP est disponible, développée par Petko Yotov (le mainteneur et principal développeur de PmWiki) et sponsorisée par Eric Forgeot. En face, Markdown est représenté par une armada d’implémentations dans tous les langages utilisés sur le web côté serveur, et aussi en JavaScript côté client pour des prévisualisation efficace sans Ajax, mais seul Pandoc, qui n’est pas si facile à compiler sur toutes les plateformes, propose autre chose qu’un rendu en HTML.

Je vais bien sûr continuer à travailler sur le logiciel Txt2tags, mais une implémentation de la syntaxe Txt2tags en JavaScript pour un rendu live sur le net, ainsi que des readers Pandoc, car j’ai vraiment envie de programmer en Haskell, et Docutils, pour pouvoir bénéficier ensuite du sublime Sphinx, sont des projets qui me motivent de plus en plus.

Enfin, je suis toujours un peu nostalgique devant ce screenshot, parce que c’est en le voyant, avec en haut à gauche le fichier avec les balises, et en bas à droite celui avec le résultat texte brut, que j’ai pris conscience que Txt2tags faisait bien ce que j’espérais, et que comme en plus il était en Python, ce serait probablement le logiciel auquel j’allais contribuer !

Mise à jour du 27 mai 2013 : Ajout dans le tableau comparatif des logiciels de Lunamark en Lua et de Jerome en Erlang. Ajout dans le tableau de classement par syntaxe de txt2tags-php.

Mise à jour du 26 février 2014 : Ajout dans le tableau comparatif des logiciels de Doxia et XWiki Rendering en Java. Ajout dans le tableau de classement par syntaxe de marked, markdown-js, erlmarkdown et XWiki Rendering.

Et le meilleur framework web Python est… Django !

Monday, June 6th, 2011

On  observe depuis quelques temps un phénomène de “consolidation” au niveau de beaucoup de logiciels libres. En effet, le modèle du libre est par essence de permettre à chacun de faire se qu’il veut, et donc d’expérimenter et d’innover. Cette dynamique est encore plus sensible quand il s’agit de technologies nouvelles. Mais une fois cette phase d’expérimentation passée, il faut aussi en tirer des conclusions, et se rassembler pour atteindre une masse critique d’utilisateurs et de développeurs, une communauté, qui viabilise le projet sur la durée.

Ainsi Ruby on Rails a été dès 2004 le grand précurseur des frameworks web de nouvelle génération. Mais même dans le monde Ruby des projets concurrents sont apparus, dont le plus important fut Merb, un framework plus léger et plus modulaire. En décembre 2008, Merb et RoR ont fusionné pour donner Ruby on Rails 3, et ainsi rassembler les énergies et les talents.

La situation dans le monde Python, bien que beaucoup plus complexe, a pris le même chemin. En effet, il existait depuis longtemps un grand nombre de technologies web en Python, aussi bien des serveurs d’application comme CherryPy ou Paste, que des langages de templates comme Myghty, Python Server Pages ou Cheetah. On ne peut que constater cette incroyable diversité sur le site officiel de Python. Une blague classique était de dire que Python était un langage tellement simple que chaque développeur pouvait écrire son propre framework. Mais aucun ne proposait un ensemble cohérent et tout intégré… aucun sauf Zope 2, mais qui lui avait comme défaut principal d’être trop intégré !

Ainsi, si sa ZODB, une vraie base de données orientée objet, était en avance techniquement sur un ORM (Object-Relationnal Mapping), c’est-à-dire une simple surcouche pour donner une apparence objet à une base de données relationnelle (SQL), elle a néanmoins fait fuire la majorité des développeurs, qui avaient comme contrainte non négociable de s’interfacer avec des bases de données MySQL, PostgreSQL ou Oracle préexistantes. De plus Zope 2 est connu pour sa courbe d’apprentissage en “Z” (Z-shaped learning curve), qui vous donne d’abord l’impression de régresser et nécessite beaucoup de temps et d’efforts avant d’obtenir un premier résultat intéressant, ainsi que pour mériter le fameux qualificatif “d’usine à gaz logicielle”.

En janvier 2006, Stéphane Fermigier, président fondateur de Nuxeo, essayait d’y voir plus clair grâce à ce schéma :

On peut identifier cinq concurrents directs, de type framework web complet : CPS, Django, TurboGears, Subway et Pylons, ainsi qu’un serveur d’application moderne orienté composant, n’attendant que de servir de base pour de nouveaux frameworks : Zope 3.

Que sont-ils tous devenus ? Il y a d’abord ceux qui sont morts, Subway, de manière classique dans le monde du libre, par désintérêt (des développeurs et/ou des utilisateurs), et CPS, de manière plus originale, en se faisant “seppuku” (d’un point de vue pythonique), en décidant de changer de base technologique, passant de Zope 2 et Python à J2EE et Java.

Restaient donc trois frameworks, Django, un ensemble complet et monolithique d’un côté, et TurboGears et Pylons de l’autre, qui essayaient quant à eux de réutiliser au maximum les nombreux projets python existants. Ainsi, Django a souvent été accusé de souffrir du syndrome NIH (Not Invented Here), voir l’intéressante conférence de Mark Ramm (principal développeur de TurboGears) sur le sujet lors du DjangoCon 2008. Il y eu donc naturellement un rapprochement entre TurboGears et Pylons, et en juin 2007 Mark Ramm annonça que le futur TurboGears 2 serait basé sur Pylons. au grand désarroi de CherryPy.

D’un autre côté apparurent plusieurs frameworks, dont Grok et repoze.bfg, prenant comme base technologique Zope 3, mais en essayant de le rendre plus facile à utiliser et à configurer, par exemple en vous préservant de trop nombreux fichiers ZCML. Et voilà qu’en novembre 2010, Pylons et repoze.bfg fusionnent pour donner naissance à un nouveau framework soutenu par le Projet Pylons et appelé Pyramid, qui se base sur le code source de repoze.bfg et son architecture à composant, Pylons ne pouvant plus évoluer à cause d’un problème de design. Enfin, en décembre, c’est TurboGears qui fusionne avec Pyramid.

Trois frameworks qui ne font donc plus qu’un, espérant qu’en additionnant toutes leurs forces ils pourront constituer un adversaire crédible à Django. Mais un survol du tutoriel rapide m’a fait froid dans le dos : du SQL à la main (c’est bien la dernière chose que je souhaitait revivre) et des décorateurs pour spécifier le langage de template pour chaque view !? Comme en plus je ne suis pas convaincu que l’architecture à composant enthousiasme grand monde, j’ai de gros doutes sur l’avenir de Pyramid.

On en est donc arrivé à un point où les concurrents de Django ont soit disparu, soit fusionné. Alors qu’il a lui continué son chemin sans trop d’embûches, en étant en particulier extrêmement stable technologiquement. Imaginez le cauchemard du développeur TurboGears, qui a d’abord appris Kid, CherryPy et SQLObject dans la version 1, puis Genshi, Paste et SQLAlchemy dans la version 2, et qui doit maintenant ouvrir son esprit à l’architecture composant de Pyramid ! Les développeurs Django n’ont eux jamais eu à vivre ce genre de choses, et ce à mon avis grâce aux excellents choix de design logiciel faits dès le départ :

  1. Le langage de template de Django est devenu un standard, utilisé par le Google App Engine et repris dans de nombreuses autres implémentations comme Jinja et le langage de template de Tornado en Python, ErlyDTL en Erlang, Twig (le langage de template de Symfony2) et H2O en PHP, Liquid en Ruby, Dotiac en Perl, Jangod en Java, NDjango en F#, ou encore djangode de node.js et le langage de template de Dojo en JavaScript. Cette réussite est principalement due à la simplicité de ce langage de template qui est “stupide”, ce qui force à mettre la logique de traitement hors des templates, et n‘a pas une  syntaxe XML, ce qui le rend utilisable facilement par des humains. Enfin, en janvier 2006 c’est le créateur de Python, Guido Van Rossum en personne, qui le consacre comme son choix personnel.
  2. Beaucoup décriaient l’ORM de Django, qu’aucun autre projet n’utilisait, et conseillaient l’utilisation de SQLobject -un changement-… puis de SQLAlchemy -un deuxième changement-, juste pour faire comme les autres frameworks Python… Puis les bases de données NoSQL sont arrivées, que ni SQLobject, ni SQLAlchemy n’ont vocation de supporter (ce n’est plus du SQL !), alors que l’ORM de Django s’adapte elle à la situation grâce au projet Django-nonrel, qui sera je l’espère bientôt intégré dans la branche principale.
  3. Le choix standard du langage de template et de l’ORM a permis et encouragé l’émergence d’un écosystème de nombreuses applications, toutes compatibles entre elles, et de snippets permettant aux développeurs de ne pas sans cesse réinventer la roue logicielle. De plus ils peuvent se baser sur l’un des nombreux sites libres disponibles pour créer les leurs. À l’opposé d’un Pylons qui laissait la liberté de ces choix, et qui n’a donc jamais pu bénéficier d’une dynamique comparable, Django est devenu une plateforme de développement autonome et incroyablement riche.

Ce développement cohérent et linéaire depuis 2005 a permis la constitution d’un large communauté et rendu Django suffisamment crédible et répandu en entreprise pour que l’on puisse trouver assez facilement un travail, un job, un emploi, ou encore un autre travail ou un autre job, de développeur rémunéré à plein temps pour travailler sur une technologie libre qu’il aime (je n’ai pas dit faire du libre), ce qui est encore trop rare.

Alors bien sûr vous pouvez aussi utiliser web2py, Tornado pour faire de l’asynchrone, un des nombreux microframeworks, ou encore créer le vôtre avec WebOb, si vous êtes bons, vous ferez d’excellentes choses. Mais LE meilleur framework web Python, c’est Django !

Mise à jour du 28 juillet 2011 : J’ai rajouté trois liens dans l’article. De plus, Georges Racinet m’a gentiment contacté pour me faire part du fait qu’il est mainteneur de CPS depuis trois ans, et que si le projet a “une communauté restreinte et peu de visibilité, il n’est pas mort et il y a du dév actif dessus”.

Head et tail de liste en Python 3

Saturday, April 23rd, 2011

Bien que Python ne soit pas un langage de programmation fonctionnelle, il incorpore un certain nombre de fonctionnalités typiques de ces langages. Ainsi les fonctions anonymes lambda, la compréhension de liste, ou encore les fonctions built-in map(), filter() et reduce() (cette dernière ayant été supprimée dans Python 3).

La programmation fonctionnelle est caractérisée par une utilisation intensive des listes, et en particulier par de nombreuses opérations récursives sur ces dernières. On est donc souvent amené à vouloir récupérer le premier élément d’une liste, sa tête ou head, et l’ensemble des éléments restants, sa queue ou tail. Ce qui s’écrit donc trivialement avec la fonction f() retournant une liste :

head, tail = f()[0], f()[1:]

Le premier problème ici est le double appel de la fonction f(), que l’on peut facilement corriger grâce à l’utilisation d’une variable intermédiaire result :

result = f()
head, tail = result[0], result[1:]

Le second problème est la lourdeur syntaxique de l’ensemble, avec la variable intermédiaire et le double usage du [] (le __getitem__ des listes en Python). On aimerait bien à la place avoir une syntaxe plus adaptée, c’est-à-dire plus proche de celles utilisées par les langages de programmation fonctionnelle, comme par exemple Erlang :

[Head|Tail] = Result

Et comme toujours avec Python, le langage se bonifie grâce à une PEP (Python Enhancement Proposal), en l’occurence la PEP 3132 intitulée Extended Iterable Unpacking. Ainsi, depuis Python 3.0, on peut utiliser une nouvelle syntaxe qui reprend celle des star-args (*args) des fonctions Python :

head, *tail = result

Ceci se teste facilement dans un shell Python 3 :

>>> result = [1, 2, 3, 4]
>>> head, *tail = result
>>> head
>>> 1
>>> tail
>>> [2, 3, 4]

On a donc maintenant une syntaxe tout à fait satisfaisante, très lisible et facilitant un style de programmation fonctionnel. Ce qui est encore plus intéressant avec cette notation, c’est qu’elle ne se limite pas au couple head et tail, et peut ainsi être déclinée de bien des manières tout à fait intéressantes comme par exemple :

>>> result = [1, 2, 3, 4]
>>> head, *body, tail = result
>>> head
>>> 1
>>> body
>>> [2, 3]
>>> tail
>>> 4

Python 3 est sorti depuis plus de deux ans et, sans être révolutionnaire, il apporte de nombreuses améliorations à Python 2 qui valent le coup d’être découvertes et utilisées !