PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

map(), filter() et reduce () ?

jeudi 14 novembre 2013 à 10:44

map(), filter() et reduce() sont des fonctions de traitement d’itérables typiques de la programmation fonctionnelle, qui ont été marquées comme à retirer des builtins pour Python 3. Finalement, seule reduce() sera déplacée dans le module functools pour Python 3.

Les opérations que font ces fonctions sont typiquement quelque chose que l’ont peut faire sans elles, et nous allons les passer en revue pour voir dans quels cas elles sont pertinentes, dans quel cas une alternative est meilleure. L’alternative étant, dans 90% des cas, une liste en intention.

filter()

filter() prend une fonction en paramètre, souvent une lambda, comme ses deux soeurs puisqu’on est dans le paradigme fonctionnel. Elle doit renvoyer True si on garde un élément, et False sinon.

L’usage typique est celui-ci :

ages = range(30)
majeurs = filter(lambda x: x > 18, ages)
print(majeurs)
## [19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]

Typiquement, ce code peut être remplacé par une liste en intention dans le pur style Python :

majeurs = [a for a in ages if a > 18]

Le code est plus court, et fait usage d’une des fonctionalités les plus importantes du langage. On peut en plus ajouter une transformation à a facilement si on le désire, au lieu de devoir coller un map() derrière.

filter() est vraiment la moins utile des 3, et sera une question de style, surtout pour les nostalgiques de Lisp. Je répète souvent que quand on code avec un autre langage, on doit essayer de se tenir au style du nouveau et pas faire un mix avec ses anciennes habitudes. Quand je code en Java, je fais des getter et setter, même si j’ai horreur de ça.

Mon conseil: oubliez filter().

map()

Si filter() est l’équivalent de la partie de droite d’une liste en intention, map() est l’équivalent de la partie de gauche. La fonction passée retourne un résultat qui permet de transformer la liste.

Typiquement :

memes = ["It's over 9000 !", "All your base are belong to us."]
print(map(unicode.upper, memes))

Ce qui peut se traduire par :

print(s.upper() for s in memes)

map() est un peu plus utile, dans le sens où sa syntaxe peut être plus concise dans certains cas, comme le casting de types. Par exemple si je reçois une heure sous forme de string :

h, m, s = map(int, '8:19:22'.split(':'))

sera plus court et plus concis, et plus clair que :

h, m, s = (int(i) for i in '8:19:22'.split(':'))

Mais bon, la différence n’est pas non plus incroyable au point d’en faire une fonctionnalitéé clé. Je l’utilise de temps à autre par soucis de brièveté, mais vraiment c’est tout.

reduce()

reduce() est plus tordu. La fonction doit prendre deux paramètres en entrée, et retourner une valeur. Au premier appel, les deux premiers éléments de l’itérable sont passés en paramètres. Ensuite, le résultat de cet appel et l’élément suivant sont passés en paramètre, et ainsi de suite.

Vous n’avez rien pigé ? C’est normal. reduce() est parfaitement cryptique. Voici ce que ça donne en pratique :

def afficher(a, b):
    print("Entrée :", a, b)
    print("Sortie :", a + b)
    return a + b
 
res = reduce(afficher, range(10))
print("Résultat final", res)
 
## Entrée : 0 1
## Sortie : 1
## Entrée : 1 2
## Sortie : 3
## Entrée : 3 3
## Sortie : 6
## Entrée : 6 4
## Sortie : 10
## Entrée : 10 5
## Sortie : 15
## Entrée : 15 6
## Sortie : 21
## Entrée : 21 7
## Sortie : 28
## Entrée : 28 8
## Sortie : 36
## Entrée : 36 9
## Sortie : 45
## Résultat final 45

Vous allez me dire, à quoi ça sert ? Et bien par exemple à appliquer des opérateurs commutatifs, ici nous l’avons fait avec +, nous avons fait la somme de tous les éléments retournés par range(10). La preuve :

print(sum(range(10)))
## 45

Il n’y a pas, en Python, de fonction équivalent à sum() pour la multiplication. Donc on ferait :

print(reduce(lambda a, b: a * b, range(1, 11)))
## 3628800

Ce qui multiplie tous les éléments entre eux. Comme l’ordre dans lequel les éléments sont multipliés n’a pas d’important (d’où le ‘commutatif’), ça fonctionne.

reduce() peut prendre un troisième paramètre, initial, qui sera la valeur passée en premier au premier appel de la fonction. Cela permet de travailler sur des calculs en cascade qui ne fonctionneraient sinon pas. Revenons à notre exemple de temps :

temps = map(int, '8:19:22'.split(':'))
print(reduce(lambda a, b: a * 60 + b, temps, 0))
## 29962

Ce qui peut se traduire par :

h, m, s = map(int, '8:19:22'.split(':'))
print(h * 3600 + m * 60 + s)
## 29962

Bien sûr, cette conversion ne fonctionnerait pas si le calcul était sur un itérable plus long. Mais une version itérative est facile à faire :

res = 0
for i in map(int, '8:19:22'.split(':')):
    res = res * 60 + i
print(res)
## 29962

Maintenant, autant les deux dernières versions sont faciles à comprendre, autant la première prend quelques secondes. Et c’est la raison pour laquelle reduce() a été retirée des builtins, pour encourager l’usage des alternatives. En effet, cette fonction donne toujours un résultat très peu lisible. Je cite et approuve Guido là dessus:

C’est en fait celle que je déteste le plus, car, à part pour quelques exemples impliquant + ou *, presque chaque fois que je vois un appel à reduce() avec une fonction non-triviale passée en argument, j’ai besoin de prendre un crayon et un papier pour faire le diagramme de ce qui est effectivement entrée dans la fonction avant que je comprenne ce qu’est supposé faire reduce(). Donc à mes yeux, l’application de reduce() est plutôt limitée à des opérateurs associatifs, et dans d’autres cas il est mieux d’écrire une boucle d’accumulation explicitement.

Graissage maison.

Bref, reduce() est dur à lire, et une boucle ne l’est pas. Écrivez 3 lignes de plus, ça ne va pas vous tuer. Relire votre one-liner dans un mois par contre…

Cette fonction a été beaucoup utilisée avec les opérateurs or et and pour savoir si tous les éléments étaient vrais au moins un élément vrai dans une liste :

tout_est_vrai = [1, 1, 1, 1]
certains_sont_vrais = [1, 0, 1, 0]
tout_est_faux = [0, 0, 0, 0]
 
# Retourne True si tout est vrai
print(bool(reduce(lambda a, b: a and b, tout_est_vrai)))
## True
print(bool(reduce(lambda a, b: a and b, certains_sont_vrais)))
## False
print(bool(reduce(lambda a, b: a and b, tout_est_faux)))
## False
 
# Retourne True si au moins un élément est vrai
print(bool(reduce(lambda a, b: a or b, tout_est_vrai)))
## True
print(bool(reduce(lambda a, b: a or b, certains_sont_vrais)))
## True
print(bool(reduce(lambda a, b: a or b, tout_est_faux)))
## False

Mais aujourd’hui, c’est parfaitement inutile puisque nous avons les fonctions built-in all() et any(), qui font ça en plus court et plus rapide :

# Retourne True si tout est vrai
print(all(tout_est_vrai))
## True
print(all(certains_sont_vrais))
## False
print(all(tout_est_faux))
## False
 
# Retourne True si au moins un élément est vrai
print(any(tout_est_vrai))
## True
print(any(certains_sont_vrais))
## True
print(any(tout_est_faux))
## False

Petite astuce finale

Souvenez-vous également que les fonctions Python peuvent être déclarées n’importe où à la volée, même dans une autre fonction, une classe, une méthode, un context manager, etc. Or une fonction peut retourner un générateur grâce à yield, ce qui vous permet de déclarer des gros bouts de logique, et de les plugger dans votre process itérative a posteriori :

def traitement_complexe(iterable):
    for x in iterable:
        if x not in (1, 3, 7) and x % 2 != 0:
            if x + x < 13 :
                yield x
            else: 
                yield x - 2
 
print("-".join(map(str, traitement_complexe(range(20)))))
## 5-7-9-11-13-15-17

flattr this!

Postez votre espace de travail !

mercredi 13 novembre 2013 à 03:46

Oui je sais vous l’avez vu 3 milliards de fois sur les forums, c’est ringard, c’est pas un article constructif mais bon tout à l’heure j’ai pris mon bureau en photo et je me suis dis que ça pouvais être marrant de voir dans quelle porcherie travaille les lecteurs de ce blog :)
D’ailleurs ça serait bien de justifier le choix de votre matos, pas juste une photo, pourquoi 8 écrans à fusion plasma ou un iCore32 à double arbre à cames, etc.

N’oubliez pas de vérifier les données EXIF de votre photo avant de poster des fois qu’il s’y trouve votre location géographique / taille d’anus / empreinte digitale fourni par aippeule et Cie ^^

Aller je me lance, Mongeois! Saint-Denis !

La téloche c'est pour LoL...

- Un ptit Mac Book Air double coeur avec 2GB de RAM et SSD. Je l’ai pris car il est vraiment compact, pour les voyages c’est top, et puis pour l’OS, y a pas à chier cet OS est formidabuleux.
- La téloche je viens de la piquer à l’hotel, résolution moyenne mais ça suffit pour les lignes de code ou y mettre les shells (et puis pour LoL que je viens de réinstaller, j’avoue…). Un peu grosse cependant je sais pas si je vais supporter.
- Souris Raser pas mal mais un peu grosse avec tapis spécial pour douleur au poignet, jamais su si c’était l’abus de souris ou de branlette.

flattr this!

Rooter son lecteur MP3 avec rockbox

mardi 12 novembre 2013 à 08:04

J’ai acheté un Sansa Clip car je cherchais un lecteur mp3 cheap, léger, avec pas mal de batterie et solide, ayant, comme Max, laissé tombé les smartphones.

Globalement j’en étais content : le joujou n’a pas besoin d’un imachin pour tourner, c’est reconnu comme clé USB. Ça lit les formats audio exotiques, ça ne pèse rien et il est déjà tombé 50 fois par terre sans broncher. Je n’ai par ailleurs toujours pas eu à le recharger.

Seulement un matin, en plein footing, monsieurs s’est mis à rebooter à chaque fois qu’il lisait une chanson de Katy Perry. Certains argueront qu’il a bon goût, mais personnellement j’aime l’avoir pour les pointes de vitesse moyennes, Dragon Force étant utilisé pour les sprints.

Bref, soft reset, hard reset, formatage de la mémoire, utilisation de SD, rien n’y fait.

Alors je me souviens de RockBox, un OS libre pour lecteur mp3. Je me dis que pour le prix, je risque pas grand chose à le flasher, une vraie brique coûterait plus cher.

Je télécharge l’installeur automatique (qui existe sous Win/Mac/Linux \o/), je branche le bousin en USB, et je root le lecteur en un clic.

Ah ! Je m’attendais à ce que ça foire moi… A devoir chercher un peu… Je sais pas, quelque chose !

Mais non, ça marche, c’est tout.

J’ai maintenant un dual boot avec l’ancien OS et Rockbox (qui se lance par défaut). Mon lecteur ne reboot plus pour rien, toutes les features d’origine sont prises en charge, avec en prime la lecture du son des vidéos (je podcast donc maintenant TED) et un son que je peux monter beaucoup plus fort.

Merveilleux.

Rien à redire.

Rien à ajouter.

flattr this!

La stack techno qu’on utilise pour faire un site Web, et pourquoi

lundi 11 novembre 2013 à 07:40

Une stack techno n’est pas une référence. Il n’y a pas de combo absolu qui rox absolument tout, c’est une question de contexte technique, financier, humain…

Mais c’est vrai que ça aide bien d’avoir sous les yeux les pratiques des autres.

Je ne vais pas expliquer pourquoi Python, je l’ai déjà fait.

Commençons plutôt par la partie purement Web, pour laquelle on utilise Django, le framework Web Python.

Max et moi avons tout deux fait du PHP avant, j’ai tâté des frameworks internes, du Symfony et plus tard du Zope. J’ai regardé du côté de Pyramid et de ses prédécesseurs, et Django est celui qui me plaît le plus. J’ai juste un peu forcé la main à Max :-)

Car oui, le framework a été avant tout un choix de goût.

Ce n’est pas un choix de performances : le framework n’a aucun impact dessus. Aucun. Les architectures ont un impact. Le framework, non. Votre bottleneck sera sur les IO, pas sur le CPU. Le choix de technos asynchrones peut avoir un impact, mais ce n’est pas une question de framework. Tornado, Twisted ou NodeJS, on s’en fout.

Donc Django, essentiellement parce qu’il me plait. Et il me plaît pour ces raisons :

En terme de base de données, on a fait du MySQL pendant longtemps. Ça a plutôt bien marché. Maintenant je commence mes nouveaux projets avec PostGres, qui est plus solide. Parfois je fais juste du Sqlite, parce que ça suffit.

Pas de NoSQL. Après plusieurs expériences avec MongoDB et CouchDB, je n’ai pas été convaincu que les bénéfices dépassaient le coût. Il faudrait un article complet là dessus (qu’on m’a d’ailleurs demandé).

Question OS. c’est du CentOS avec Max (il a plus l’habitude) ou du Ubuntu Server pour mes autres projets. Je reste sur les LTS. Ce n’est pas un choix très réfléchi, c’est surtout par habitude.

Pas de machine virtuelle. On a essayé, sans y trouver un grand intérêt :

Et donc pour le déploiement, j’utilise fabric, avec fabtools.

Ce n’est pas la solution la plus efficace, d’autant que ça limite à Python 2.7, mais c’est la plus simple. C’est juste du code Python. N’importe qui peut comprendre le déploiement en 15 minutes. Ça se modifie vite, s’adapte facilement.

Il faut comprendre qu’on a jamais plus d’une dizaine de serveurs pour un projet, ces choix sont donc fait en fonction de cela. Il va sans dire que si vous gérez un parc de centaines de machines, ça ne sera pas du tout le même choix technique. Peut être que Chef ou des VM seront alors carrément plus interressant. Peut être que le NoSQL et sa capacité au scalling sera bien plus rentable.

Il ne s’agit pas de décrier les technos que nous n’utilisons pas. Il s’agit juste de dire, voilà les choix que nous avons fait, dans tel contexte, pour telles (bonnes ou mauvaises) raisons.

Durant les dernières années, on a ajouté Redis à notre stack. C’est un outil fantastique qui sert à tout : de la base de données pour les trucs simples (il y a des fois ou un schéma est overkill) à la solution de caching. C’est ce qu’on a de plus proche du NoSQL.

L’outil est tellement simple à installer (vraiment le degré zero de la maintenance, c’est beau) et à utiliser que ça ne vaut juste pas le coup de s’en priver.

Du coup, plus de memcache. Toutes les grosses requêtes sont sauvegardées dans Redis, dès qu’on fait un script qui a besoin de persistance temporaire, Redis, pour communiquer entre plusieurs process, Redis, pour toutes les opérations qui ont besoin de grosses perfs comme les stats, Redis. Vive Redis.

D’ailleurs on utilise Redis aussi comme broker pour notre gestionnaire de queues et de taches : celery. Si vous pythonez, je vous recommande chaudement celery pour toutes les tâches en background, les crawlers, les chaînes de process, etc.

On a aussi du moteur de recherche. Là on tappe dans du Solr (avec haystack). C’est très puissant, en tout cas syntaxiquement car ça ne fait pas de sémantique. Ne vous attendez-donc pas à rattraper Google. Mais c’est aussi méga chiant à configurer et très lourd. Je pense qu’un jour on va migrer sur ElasticSearch, mais c’est pas la priorité. Don’t fix what ain’t broken.

Devant tout ça on a Nginx. Comme beaucoup on a fait Apache => Cherokee => lighttp => nginx. Et franchement, je ne reviendrais jamais en arrière : plus léger, plus rapide, plus facile à installer et à configurer, plus versatile. Nginx fait tout, et mieux.

En proxy on a du gunicorn. Parce qu’on avait la flemme de configurer uwsgi et qu’on a pris l’habitude.

Après on utilise plein de libs, de petits outils, etc. Mais ça c’est le gros de notre archi.

flattr this!

Preprocesser ses fichiers statiques et recharger son navigateur automatiquement avec Python livereload

dimanche 10 novembre 2013 à 07:47

Livereload est une extension multi-navigateur qui permet de recharger tout ou partie d’une page quand un fichier a changé sur le disque.

C’est très pratique pour développer un site Web puisque si vous modifiez un template, un fichier JavaScript, une image ou un fichier CSS, vous n’avez pas besoin de cliquer sur la fenêtre du navigateur et appuyez sur F5 pour voir le résultat. Si vous avez un double écran (et si vous faites du dev Web, vous devriez), vous ne quittez pas votre éditeur de code.

L’extension est gratuite, mais le serveur existe en plusieurs version. Il y a une version graphique pour Windows et Mac qui est payante. Si vous avez un peu de budget et pas envie de vous prendre la tête, achetez là et arrêtez la lecture de l’article, c’est beaucoup plus facile.

Sinon, suivez le guide pour la version gratos en ligne de commande.

Installation

Il existe une version Python en ligne de commande du serveur : Python livereload. Il y a aussi une version pour les rubistes.

Je vous invite donc à l’installer avec pip:

pip install livereload

Il vous faudra aussi l’extension de navigateur.

Après, depuis votre terminal, mettez vous dans le dossier que vous voulez surveiller (par exemple le dossier contenant vos fichiers CSS), et lancez le serveur :

livereload

Et activez l’extension pour la page que vous voulez recharger automatiquement. Normalement, c’est juste un clic sur un bouton.

C’est bon, votre page devrait recharger automatiquement.

Rechargement à la carte

On peut choisir ce qu’on va recharger plus précisément en créant un fichier de configuration.

Créez un fichier de code Python nommé “Guardfile”, sans l’extension “.py”. Il va ressembler à ceci :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
from livereload.task import Task
 
# watcher les js ou les css
Task.add('chemin/relatif/vers/fichier/a/surveiller.css')
Task.add('chemin/relatif/vers/fichier/a/surveiller.js')
 
# watcher les images ou les templates
Task.add('chemin/relatif/vers/dossier/a/surveiller')

Et lancez la commande livereload en étant dans le même dossier que ce fichier. Notez que le serveur ne parse ce fichier que quand l’extension est activée et que vous avez visité la page au moins une fois.

On peut même demander d’effectuer des tâches avant le rechargement de la page. Cela peut être des tâches complètement arbitraires, mais des raccourcis existent pour les tâches les plus courantes, telle que minifier du JS ou compiler un pre-processeur CSS.

Par exemple, j’utilise cette fonctionnalité pour compiler mes fichiers LESS CSS à chaque modification.

Pour cela, il faut installer le compilateur LESS. Sous Ubuntu, ça se fait en deux coups de cuillère à pot :

sudo apt-get install npm
sudo npm install -g less

Et dans le Guardfile, il faut ajouter un code du style :

from livereload.task import Task
from livereload.compiler import lessc
 
Task.add('../apps/core/static/less/boostrap/boostrap.less',
         lessc('../apps/core/static/less/boostrap/boostrap.less',
               '../apps/core/static/css/boostrap.css'))

Il y a un a tas d’options donc checkez la doc, mais aussi le code source car la doc n’est pas exhaustive.

flattr this!