Posts Tagged ‘Python’

Ruby est mourant (Python, Node.js, Go et Elixir l’ont tué)

lundi, décembre 5th, 2016

En août 2015 GitHub publiait le classement des langage de programmation les plus populaires depuis son lancement en 2008. L’article mettait l’accent sur la progression de Java, lié à l’adoption des services de GitHub par les entreprises friandes de ce langage, mais c’est bien l’évolution de la popularité de Ruby qui m’avait le plus interpellé.

Leader aux premiers jours de 2008, avec en particulier la présence immédiate de Ruby on Rails sur GitHub (GitHub est une application Ruby en Rails), Ruby est passé 2ème en 2013, puis 3ème en 2014 et enfin 4ème en 2016.

github

Suivant de près la popularité des langages sur GitHub, j’ai eu la chance de capturer hier l’improbable moment où PHP – le grand-père du web en net regain de forme depuis la sortie de PHP 7 – a dépassé l’enfant prodige pour le reléguer à la 5ème place !

classment2

Mais si Ruby est mourant, quels sont donc ses meurtriers ?

Python pour la diversité d’utilisation

Python est un peu le concurrent historique de Ruby, tant ces deux langages semblaient occuper la même « niche » de langage de programmation moderne, dynamique et orienté objet. Ce que l’on peut faire avec Python on peut le faire avec Ruby, et réciproquement, le choix entre les deux est plus une question de feeling sur la syntaxe du langage ou l’ambiance de la communauté.

Oui mais justement, que fait-on avec ces langages ? En première page du site web du langage Python on peut trouver une section « Use Python for… » qui donne une réponse à cette question dans le but de conforter les débutants dans leur choix de Python : ils ne vont pas seulement apprendre un langage de programmation puissant et facile d’utilisation, mais aussi un outil pour faire plein de choses utiles et très diverses.

python

Car si Python est bien sûr utilisé pour la programmation web et qu’il a son alter ego de Rails avec Django, il est aussi énormément utilisé dans bien d’autres domaines. Python est installé par défaut sur toutes les distributions GNU/Linux et sur macOS, il est largement utilisé en administration système, s’est imposé pour des utilisations scientifiques et pédagogiques et représente le choix le plus simple et efficace pour développer des interface graphiques, avec de nombreux bindings vraiment fonctionnels et maintenus.

Ruby et sa communauté sont en comparaison par trop web-centric. Rails, Sinatra et Jekyll sont formidables, et ils étaient même très innovants, mais de nombreux projets s’en sont inspiré, des équivalents ont été développés dans d’autres langages et ils ne peuvent plus à eux seuls justifier d’apprendre le Ruby. Ruby a percé grâce à Rails et au web, mais il n’a jamais su s’en émanciper et c’est ce qui est en train de causer sa perte.

Node.js pour le buzz

D’une manière qui a parfois pu irriter les pythonistes, Ruby a longtemps eu l’image d’un langage cool, et tout ce qui s’y rapportait de près ou de loin faisait facilement le buzz. Oui mais voilà, Node.js est passé par là, amenant au JavaScript l’ubiquité, cette exceptionnelle singularité d’être le seul langage de programmation présent à la fois côté serveur et côté client.

trend

Node.js a bénéficié d’une large et dynamique communauté préexistante de développeurs JavaScript, ce qui a permis son adoption à une vitesse incomparable à celle d’un « nouvel entrant ». Node.js a donc attaqué frontalement Ruby sur sa niche web tenue par Ruby on Rails et l’a en quelque sorte dépossédé de son buzz.

Go et Elixir pour les performances

Deux langages de programmation récents ont aussi causé beaucoup de tort à Ruby en l’attaquant sur le terrain des performances, son point faible historique. On peut ainsi développer rapidement de beaux et fonctionnels sites web avec Ruby on Rails, mais quand le succès arrive, il est alors très difficile de scaler. Le remplacement de Ruby on Rails par du Java par Twitter en 2011 en est bien sûr l’exemple le plus cinglant.

Go est un langage de programmation compilé créé par Google en 2007 et rendu disponible en 2009. Alors que l’on pensait que ce langage allait attirer les développeurs C et C++ à la recherche de plus de confort, il a en fait attiré beaucoup de développeurs de langages dynamiques à la recherche de plus de performances. Ainsi de nombreuses personnes réécrivent leur application Rails en Go et constatent des gains de performances leur permettant par exemple de passer de 30 à 2 serveurs pour l’hébergement, ce qui implique des économies de travail, de temps et d’argent colossales.

Elixir est un langage de programmation fonctionnel datant de 2012 qui utilise BEAM, la formidable machine virtuelle massivement concurrente développée pour Erlang. La particularité d’Elixir est de reprendre la sémantique d’Erlang et de la proposer aux développeurs sous la forme d’une nouvelle syntaxe… inspirée du Ruby ! Cela n’est pas très surprenant quand on sait qu’Elixir a été créé par José Valim, un des principaux développeurs de Ruby on Rails. Et ce sont bien sûr les problèmes de performance de Ruby qui ont motivé sa démarche. Elixir se positionne donc clairement comme un nouveau Ruby, mais qui scale.

Il est très probable que Go et Elixir vont continuer à devenir de plus en plus populaires dans les années qui viennent, et que Ruby va avoir beaucoup de difficultés à s’en relever. Un dernier petit exemple en passant, sur les cinq logiciels libres dont je parlais dans mon article sur la base LEGI, deux sont en Python, un en PHP, un en Go, un en Julia… et aucun en Ruby.

Les micro-frameworks Python : Flask, Bottle, Itty, Newf, djng et importd

dimanche, août 21st, 2016

Suite au succès phénoménal de Flask, qui a plus d’étoiles sur GitHub que Django, et à celui remarquable de Bottle, j’ai poursuivi ma réflexion personnelle sur les microframeworks. C’est un sujet auquel je m’étais fortement intéressé en 2009, il y a 7 ans donc, lors de la soudaine prolifération de microframeworks Python.

Sinatra, l’inspiration vint encore du Ruby

Tout comme Ruby on Rails a été dès 2004 le grand précurseur des frameworks web MVC de nouvelle génération, Sinatra a été dès 2007 le précurseur des microframeworks web. Sinatra a rendu l’écriture d’application web radicalement plus aisée grâce à sa syntaxe claire et concise :

require 'sinatra'

get '/' do
'Hello world!'
end

WSGI, la condition technique

Le standard Python WSGI (Web Server Gateway Interface) décrit dans la PEP 333 de 2003, mise à jour en 2010 par la PEP 3333, a proposé une interface simple permettant de découpler les frameworks web des serveurs web.

Et l’ajout du module wsgiref dans la bibliothèque standard de Python 2.5 en 2006 a permis a tout framework d’être utilisable sans aucune dépendance grâce au petit serveur WSGI qu’il fournit :

from wsgiref.simple_server import make_server

Qu’est-ce qu’un micro-framework ?

En admettant que l’on sache ce qu’est un framework, on doit s’interroger sur le sens de micro. Le Larousse précise :

Préfixe, du grec mikros, petit, indiquant que quelque chose est très petit.

Un micro-framework est donc un très petit framework. L’intérêt de cette démarche est réel pour le développeur car il est plus facile d’apprendre à utiliser un petit framework, qui de plus consomme moins de ressources et impose souvent moins de contraintes.

Il est communément admis que ces microframeworks sont surtout intéressants pour construire de petites applications, la quantité de fonctionnalités des frameworks full-stack justifiant de plus en plus leur usage avec l’augmentation de la taille et des besoins de l’application.

Le moins de lignes de code possible

Un microframework est donc un framework ayant une petite base de code, c’est-à-dire un petit nombre de lignes de code. Encore faut-il qu’il en ait suffisamment pour qu’un développeur ait un intérêt à l’utiliser.

Le minimum nécessaire n’est pas forcément facile à évaluer, mais on peut penser qu’un framework comme Newf, avec ses 114 lignes de code, était très nettement en dessous. Itty, quant à lui, est très proche de Bottle en termes de design et de fonctionnalités, mais d’une manière globalement plus – et donc finalement trop – minimaliste.

Dans les deux cas, cela s’est révélé insuffisant pour susciter un réel intérêt chez les développeurs d’applications web et donc pour créer une dynamique et fédérer une communauté autour de ces projets.

Tout le framework dans un seul fichier

La théorie de l’ingénierie logicielle a combattu cette pratique au nom d’une meilleure structuration, permettant une bonne séparation des fonctionnalités et donc un débogage et une réutilisation plus facile du code. Pourtant, mettre tout le code dans un seul fichier présente certains avantages, en particulier quand il s’agit de le distribuer. C’est une pratique qui a connu une nouvelle légitimation avec le succès des bibliothèques JavaScript côté client, facilement chargées dans le navigateur web d’une simple ligne :

<script src="https://cdn.net/lib.js"></script>

Bottle est distributed as a single file module alors que Flask est plus classiquement composé de plusieurs fichiers. Flask, le plus populaire des microframeworks Python en est-il donc vraiment un ?

Essayons d’y voir plus clair à l’aide de wc récursifs appliqués sur les répertoire contenant le code source des frameworks, et desquels j’ai retiré le code des tests :

Micro-frameworksFrameworks
NomNewfdjngimportdIttyBottleFlaskTornadoPyramidDjango
wc -l1503287919584 1796 46922 32722 641122 675
sloccount1142305336322 7212 72511 53511 31277 514
Fichiers Python1142112181193853
Étoiles GitHub501234543743 87822 00711 9032 05920 717
PyPI (millions)0,10,0521411223

J’ai ajouté au tableau Django, Pyramid et Tornado, les trois frameworks web Python les plus populaires, et il apparaît clairement qu’en comparaison Flask est bien un microframework, puisque s’il est 1,5 fois plus gros que Bottle, il est 3,5 fois moins gros que Tornado et Pyramid et 19 fois moins gros que Django.

Toute l’application dans un seul fichier

Lorsque l’on utilise les outils d’aide des frameworks classiques pour débuter un projet, on se retrouve devant une arborescence complexe. Par exemple avec Django, si vous évitez les bizarreries et faites les choses normalement :

$ django-admin startproject django_project
$ cd django_project
$ django-admin startapp django_app


django_project
├── django_project
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── django_app
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── manage.py

De même avec Pyramid :

$ pcreate -s starter pyramid_project

pyramid_project
├── CHANGES.txt
├── development.ini
├── MANIFEST.in
├── production.ini
├── pyramid_project
│ ├── __init__.py
│ ├── static
│ │ ├── pyramid-16x16.png
│ │ ├── pyramid.png
│ │ ├── theme.css
│ │ └── theme.min.css
│ ├── templates
│ │ └── mytemplate.pt
│ ├── tests.py
│ └── views.py
├── README.txt
└── setup.py

Ceci est une bonne base pour un gros projet, qui sera bien structuré dès le départ, avec un bon découplage du code, et même les fichiers nécessaires à son packaging ; mais pour beaucoup de projets assez simples, c’est surtout lourd et redondant.

Tous les microframeworks ont donc comme point commun qu’ils sont conçus pour qu’une application toute entière contenue dans un seul fichier soit parfaitement fonctionnelle, et un certain nombre de frameworks qui ne prétendent pas être des microframeworks, comme Tornado, partagent la même approche.

Pas de dépendances… ou beaucoup de dépendances ?

Deux visions opposées peuvent en effet légitimement se défendre lors de la construction d’un microframework :

– pour avoir un framework léger il ne faut aucune dépendances, et se reposer exclusivement sur la bibliothèque standard de Python (Python Standard Library)
– pour avoir un framework léger il faut au contraire se reposer un maximum sur des bibliothèques existantes, fiables et populaires si possible, et se contenter d’offrir une fine couche logicielle permettant de les interfacer correctement

Newf et Itty font bien sûr partie de la première catégorie.

Bottle qui has no dependencies other than the Python Standard Library, et fournit son propre langage de template, fait donc aussi partie de la première catégorie. Mais on peut en plus lui interfacer facilement Mako, Jinja2 et Cheetah, des langages de template Python populaires, ce qui le fait alors déborder sur la deuxième catégorie.

Flask quant à lui est de la seconde catégorie, d’autant que son auteur est Armin Ronacher (mitsuhiko) qui est aussi l’auteur de ses dépendances Werkzeug et Jinja2. Mais si avoir comme dépendances une bibliothèque WSGI et un langage de template est on ne peut plus légitime pour un microframework, l’ajout de la bibliothèque d’interface de ligne de commande Click semble beaucoup plus contestable et relever plutôt du syndrome NIH (Not invented here).

Enfin, à l’initiative de djng, la seconde vision a amené à de multiples essais de microframeworks basés sur… Django, le maxi-framework du monde Python ! Et si seul importd a rencontré un certain succès parmi ces projets, je pense que cet oxymore logiciel est un concept vraiment intéressant et que son potentiel n’a probablement pas encore été totalement exploité.

Mise à jour du 10 septembre 2016 : Suite au commentaire de Stéfane Fermigier, ajout dans le tableau du comptage des lignes de code avec SLOCcount. Ajout de djng.
Mise à jour du 10 octobre 2016 : Ajout dans le tableau du nombre de téléchargements sur PyPI.

Arrêtez d’écrire des classes !

lundi, décembre 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

samedi, octobre 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

jeudi, mars 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

mardi, février 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

mardi, janvier 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()

lundi, décembre 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

jeudi, septembre 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

dimanche, août 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
AsciiDocPythonmoyenneNonDump de w3m ou de LynxGNU GPLv2 ou suivante
DeplateRubyfaibleNonOuiGNU GPLv2 ou suivante
PandocHaskellforteNonOuiGNU 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
BureautiqueTxt2tags
Pandoc
AsciiDocDocutils
Pandoc
Pandoc
Web serveurTxt2tags (Python)
txt2tags-php (PHP)
AsciiDoc (Python)Docutils (Python)Le Markdown original de John Gruber (Perl)
python-markdown (Python)
BlueCloth (Ruby)
php-markdown (PHP)
erlmarkdown (Erlang)
XWiki Rendering (Java)
Le Sundown de GitHub (C)
Web client (JavaScript)txt2tags.jsmarked
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. Grâce aux nombreux efforts de ce dernier, il existe maintenant plusieurs implémentations de la syntaxe Txt2tags en JavaScript, avec une démo parfaitement fonctionnelle des possibilités de rendu côté client en temps réel. Et Matthew Pickering a quant à lui écrit un reader Txt2tags pour Pandoc.

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évisualisations efficaces 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 dans un parser Docutils, pour toucher directement toute la communauté des développeurs Python qui documentent leurs projets, et pouvoir bénéficier ensuite du sublime Sphinx, est un projet qui me motive 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 pour Txt2tags.
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 pour Markdown.
Mise à jour du 26 février 2016 : Ajout dans le tableau de classement par syntaxe de txt2tags.js et Pandoc pour Txt2tags et de Sundown pour Markdown. Modification dans le tableau comparatif des logiciels, Pandoc passe d’une popularité moyenne à forte, et AsciiDoc d’une popularité forte à moyenne.