PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Python-like n’a aucun intérêt 17

mardi 21 avril 2015 à 08:57

On voit passer des technos ici et là qui annoncent “un langage qui a un goût de Python”, “une syntaxe Python like”, “un preprocesseur qui converti un code compatible Python en JS”, etc.

Ça n’a aucun intérêt.

Oui, la syntaxe de Python fait parti de ses points forts, d’une des raisons qu’on aime le langage. Mais la syntaxe n’est PAS le langage.

Python, c’est un standard qui signifie que mon code Python tourne sur CPython, Pypy, IronPython et Jython. Ce n’est pas qu’une syntaxe. C’est aussi l’accès à une gigantesque stdlib, à des milliers de paquet sur pypi, à de la métaprogrammation, à la capacité de créer des threads, du multiprocessing, des coroutines, d’accéder au système de fichier, d’attaquer une base de données, de traiter les dates, etc.

Ce n’est pas juste la possibilité de faire :

[x * 2 for x in iterable if condition]

Au passage, généralement les Python-like, ne font même pas ça correctement. Oubliant qu’en python on peut faire aussi :

[x * 2 for x in iterable][2:6:3]
(x * 2 for x in iterable)
next(x * 2 for x in iterable)
next((x * 2 for x in iterable), 0)
{x * 2 for x in iterable}
{x: x * 2 for x in iterable}
[x + y for x, y in iterable]
[y * 2 for x in iterable for y in x]

Bref, un truc qui fait vaguement comme Python n’est pas intéressant. Si je veux quelque chose de différent, je vais utiliser un autre langage. Si je veux Python, je vais utiliser Python. Pas un ersatz qui ne me donnera pas un dixième du potentiel d’un outil que je maîtrise très bien.

Les slices, c’est rapide et c’est beau 19

jeudi 16 avril 2015 à 10:35

Si vous faites de l’analyse de gros jeux de données en Python, vous vous êtes vite rendu compte que c’est là que le langage montre sa lenteur.

Les boucles pour manipuler des arrays d’entiers, qui prennent quelque nanosecondes en C, prennent des microsecondes en Python. C’est pour ça que les scientifiques utilisent SciPy et les analystes Pandas, qui sont tous deux basés sur la lib C Numpy bien enrobée dans du joli Python.

Parfois néanmoins, garder le code en pur Python est un objectif, comme dans le cas du projet mss, un screenshotter qui ne souhaites avoir une extension compilée attachée à la patte.

Or la manipulation d’images, ça bouffe des grosses matrices de pixels, et l’auteur nous a posé une jolie colle sur IndexError : ayant un array de pixels en notation BGR (Blue, Green Red), comment le transposer en RGB de manière performante ?

Sa proposition était :

pixels = range(300) # je réduis le nombre de pixel pour le benchmark
buffer_len = len(pixels)
 
def version1():
    for idx in range(0, buffer_len - 2, 3):
        pixels[idx + 2], pixels[idx] = pixels[idx], pixels[idx + 2]

J’ai tenté :

from array import array
xrange = getattr(__builtins__, 'xrange', range)
 
def version2():
    def to_rgb(pixels, buffer_len):
        for i in xrange(0, buffer_len - 2, 3):
            yield pixels[i + 2]
            yield pixels[i + 1]
            yield pixels[i]
 
    return array('H', to_rgb(pixels, buffer_len))

Mais mesurer révèle que cette solution est au final bien plus lente que celle de l’auteur :

import timeit
print(timeit.timeit('version1()', setup='from __main__ import version1'))
#16.9543120861
print(timeit.timeit('version2()', setup='from __main__ import version2'))
#42.7808477879

C’est bubulle qui va nous pondre une solution originale, élégante, mais surtout 5 fois plus rapide, ce qui est complètement inattendu :

def version3():
    pixels[2:buffer_len:3], pixels[0:buffer_len:3] = pixels[0:buffer_len:3], pixels[2:buffer_len:3]

Il utilise 3 astuces en une :

Le gain de perf est assez bluffant :

print(timeit.timeit('version3()', setup='from __main__ import version3'))
#1.84227705002

Pourquoi est-ce aussi rapide ? Je ne peux que faire des suppositions.

Je pense que faisant cette opération sans boucler explicitement, une bonne partie reste dans l’implémentation en C sous-jacente. On évite notamment pas mal d’appels à __getitem__ et __setitem__ qui sont toujours coûteux en Python.

Par curiosité, j’ai lancé le même test sur pypy :

0.359977960587
15.5387830734
0.750488996506

On voit l’effet de la compilation JIT puisque la boucle explicite redevient plus performante.

Mais du coup je me pose une question : pourquoi ma version, qui utilise une boucle explicite également, reste autant à la traine ?

Puis j’ai réalisé : l’array stock des entier C, mais quand on les sort, il faut les rewrapper dans des entiers Python. Donc ma boucle se tape cette conversion à chaque tour, et ça bouffe.

Je vire l’array :

def version4():
    def to_rgb(pixels, buffer_len):
        for i in xrange(0, buffer_len - 2, 3):
            yield pixels[i + 2]
            yield pixels[i + 1]
            yield pixels[i]
 
    return list(to_rgb(pixels, buffer_len))

Et pouf, le temps devient plus raisonnable.

Avec CPython : 1.84687900543
Avec Pypy : 0.274104118347

Sur CPython, la vitesse est du même ordre de grandeur que la version avec les slices.

Sur PyPy, la version est 2X plus rapide que les slices, est 25% plus rapide que la version originale.

Moralités :

Minitip pew et OSX

mercredi 15 avril 2015 à 13:27

Ceci est un post invité de coyote posté sous licence creative common 3.0 unported.

Comme tout bon lecteur de S&M, je me fais un devoir d’adopter tous les outils conseillés par nos maîtres (oui bon sauf crossbar.io – j’ai une vie…)

Donc, j’étais très heureux de découvrir pew mais soyons honnête, ce n’est pas exactement un drop-in replacement de virtualenv-wrapper. Bien sûr, c’est très proche et c’est beaucoup mieux mais… deux-trois petites bricoles n’emmerdaient toujours.

Le problème

bash sous OSX et Linux ont des configurations et conventions un poil différentes ce qui fait que par défaut, si vous n’êtes pas déjà au fait de ces différences, vous vous rendez compte que dès que vous activez un environnement avec pew, tout est cassé : votre prompt, vos raccourcis, etc.

Tout ça est dû au fait que (/!\ simplification inside) :

* Sous linux, au lancement de tout shell, le contenu du bashrc (~/.bashrc, /etc/bashrc) est exécuté.
* Sous OSX, au lancement d’un terminal, le contenu du ~/.bash_login est exécuté puis au lancement de subshell, c’est le ~/bashrc.

C’est con, mais ça veut dire qu’à chaque pew workon toto, seul .bashrc est exécuté. Et généralement, sous OSX, il est vide ou non existant.

La solution

C’est très simple, mais ça prends quand même 5mn…

On ne peut pas simplement tout déplacer du .bash_login vers le .bashrc, sinon hors virtualenv ça ne marchera pas. Voici donc comment je fais moi, mais c’est juste parce que je suis paresseux.

~/.bash_login:

source ~/.bashrc

Et c’est tout. En gros, on court-circuite le comportement OSX pour revenir vers du plus standard.

Extrait du ~/.bashrc:

# modification de diverses variables que je veux PARTOUT (hors env et dans env)
export PATH=/usr/local/mongodb-osx-x86_64-2.6.3/bin:$PATH
 
# modifications que je veux uniquement HORS env
# si vous avez d'autres versions de python ou pip dans votre path,
# comme le .bashrc est lancé après l'inclusion du path de l'environ par pew
# vous devez bricoler sinon vous aurez pas la bonne version. 
if [ "${VIRTUAL_ENV}a" = "a" ]
	then
	export PATH="/Library/Frameworks/Python.framework/Versions/3.4/bin:${PATH}"
else
        # modification voulues uniquement dans l'env ?
fi
 
# raccourcis pour les vieilles habitudes. c'est ce qui me derange
# le plus avec pew :)
function workon() {
	pew-workon $@;
}
 
# completion pew sur le raccourci. vous connaissez le nom de vos envs vous ?
_pew()
{
    local cur=${COMP_WORDS[COMP_CWORD]}
    COMPREPLY=( $(compgen -W "$(pew-ls)" -- ${cur}) )
}
complete -F _pew workon
 
# renvoie le path d'un projet depuis le nom de l'env pour faire d'une pierre deux coups (optionnel)
# utilise pyp
function pwdof() {
	path=`pwd`
	env_name=`echo $@| pyp "p.split('/')[-1]"`
	if [ "${env_name}" = "toto" ]
		then
		path=~/src/toto-project;
	fi
 
	echo $path;
}
 
# coloration du prompt a l’intérieur du subshell
# j'aime avoir les branches git et le nom de l'env dans mon prompt.
# donc pour que votre prompt marche dehors ET dedans (exemple)
function color_prompt {
    local __user_and_host="\[\033[01;32m\]\u@\h"
    local __cur_location="\[\033[01;34m\]\w"
    local __git_branch_color="\[\033[31m\]"
    local __git_branch='`git branch 2> /dev/null | grep -e ^* | sed -E  s/^\\\\\*\ \(.+\)$/\(\\\\\1\)\ /`'
    local __prompt_tail="\[\033[35m\]$"
    local __last_color="\[\033[00m\]"
    export PS1="$__user_and_host $__cur_location $__git_branch_color$__git_branch$__prompt_tail$__last_color "
    if [ "${VIRTUAL_ENV}a" != "a" ]
    	then
		export PS1="(\$(basename '$VIRTUAL_ENV'))$PS1";
 
		# facultatif: permet de faire un cd au lancement du subshell
		cd `pwdof ${VIRTUAL_ENV}`
   	fi
}
color_prompt
 
# completion de la commande pew qui n'est pas installé par pip
# mais qui reste pratique.
# adapté depuis: https://github.com/berdario/pew/blob/master/pew/complete_scripts/complete.bash
_pew()
{
	local cur=${COMP_WORDS[COMP_CWORD]}
	local prev=${COMP_WORDS[COMP_CWORD-1]}
    args="--help --python -i -a -r"
    commands="ls add mkproject rm lssitepackages cp workon new mktmpenv setproject show wipeenv sitepackages_dir inall toggleglobalsitepackages"
 
    case $prev in
        ls|show|rm|workon|cp|setproject)
            COMPREPLY=( $(compgen -W "$(pew-ls)" -- ${cur}) )
            return 0
            ;;
        inall)
            _command_offset 2
            return 0
            ;;
        mktmpenv|new)
            COMPREPLY=( $(compgen -W "${args}" -- ${cur}) )
            return 0
            ;;
        mkproject)
            COMPREPLY=( $(compgen -W "${args} -t --list" -- ${cur}) )
            return 0
            ;;
        add)
            COMPREPLY=( $(compgen -W "--help -d" -- ${cur}) )
            return 0
            ;;
    esac
 
    COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
 
}
complete -F _pew pew

Et voilà !

Ça casse pas trois pattes à un canard mais si vous êtes sous OSX, que vous avez installé, vu que c’était pas parfait puis vous êtes dit «on verra plus tard» et bien le moment est arrivé.

Un header d’encoding plus simple pour Python 11

mardi 14 avril 2015 à 14:15

Je sais pas pour vous, mais moi je me souviens jamais de :

# -*- coding: utf-8 -*-

Je le copie/colle à chaque fois, et sur sublime j’ai un snippet pour le taper.

Je viens d’apprendre, après 10 putain d’années à coder en python, que le header suivant était parfaitement valide :

# coding: utf-8

Bordel de merde, pourquoi c’est pas écrit dans tous les tutos ? Pourquoi on se tape encore l’ancien ?

Évidement avec Python 3, y a souvent plus besoin d’en-tête du tout, mais shit, ce genre détail c’est super con. Vous imaginez pas le nombre d’élèves à qui j’ai fait apprendre la première version toute pourrie alors que la seconde est si simple.

Grr, dirais-je.

Le don du mois : moi 12

lundi 13 avril 2015 à 10:50

Je n’ai pas de thune ce mois ci, étant très, très à découvert, ce qui est directement une conséquence d’être à son compte : les clients ne paient pas toujours à temps, quand ils paient, et je passe le permis moto ce qui me rajoute des frais.

(Je note par ailleurs que ma strat de révision des exams n’a pas changé depuis le collège. Putain que je suis un branleur, ça n’a rien à voir avec l’adolescence.)

Du coup, pas de don.

Je publie quand même l’article pour rappeler la philosophie du don : ce n’est pas une obligation, ce n’est pas un devoir, c’est quelque chose de volontaire qu’on fait selon ses capacités.

Ne culpabilisez pas de ne pas donner. Si vous donnez 1 euros une fois dans l’année, c’est très bien.