Sam & Max » with http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Batbelt, la lib des petits outils Python qui vont bien 15 http://sametmax.com/batbelt-la-lib-des-petits-outils-python-qui-vont-bien/ http://sametmax.com/batbelt-la-lib-des-petits-outils-python-qui-vont-bien/#comments Mon, 03 Jun 2013 08:57:33 +0000 http://sametmax.com/?p=6327 A force de coder plein de projets, il y a des opérations qui reviennent très souvent. Ces traitements sont petits et complètement sans relation, difficile d’en faire quelque chose. J’ai tout de même finit par en faire un lib, batbelt, qui au final n’est qu’une grosse collections de snippets que j’utilise régulièrement. Il y a aussi des trucs que j’utilise moins ou des astuces / hacks un peu crades, c’est toujours pratique pour geeker à l’arrache vite fait. Vous allez d’ailleurs retrouver des bouts de code dont j’ai déjà parlé sur le site

pip install batbelt

Et la plupart des fonctions sont accessible avec un from batbelt import...

Voici les choses qui pourraient vous intéresser le plus dans batbelt…

To timestamp

Mais combien de fois j’ai du la coder celle-là ? En plus l’inverse existe, alors pourquoi, mon Dieu, pourquoi ?

>>> from datetime import datetime
>>> to_timestamp(datetime(2000, 1, 1, 2, 1, 1))
946692061
>>> datetime.fromtimestamp(946688461) # tu as codé celle là et pas l'autre connard !
datetime.datetime(2000, 1, 1, 2, 1, 1)

Récupérer une valeur dans une structure de données imbriquée

Au lieu de faire :

try:
    res = data['cle'][0]['autre cle'][1]
except (KeyError, IndexError):
    res = "valeur"

On peut faire :

get(data, 'cle', 0, 'autre cle', 1, default="valeur")

Récupérer la valeur d’un attribut dans un attribut dans un attribut…

Pareil, mais pour les attributs.

try:
    devise = voiture.moteur.prix.devise
except AttributeError:
    devise = "euro"

On peut faire :

devise = attr(voiture, 'moteur', 'prix', 'devise', default='euro')

Itérons, mon bon

Ces fonctions retournent des générateurs qui permettent d’itérer par morceau ou par fenêtre glissante.

>>> for chunk in chunks(l, 3):
...     print list(chunk)
...
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
>>> for slide in window(l, 3):
...     print list(slide)
...
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]

Ça devrait être en standard dans Python.

Parfois on veut juste le premier élément d’une collection. Ou juste le premier à être vrai:

>>> first(xrange(10))
0
>>> first_true(xrange(10))
1

Marche avec n’importe quel itérable, contrairement à [0] qui ne marche que sur les indexables. Et en prime on peut spécifier une valeur par défaut:

>>> first([], default="What the one thing we say to the God of Death ?")
'What the one thing we say to the God of Death ?'

Set ordonné

On a des dicts ordonnés dans la lib standard, mais pas de set ordonné. On en a pas besoin souvent, mais ça peut être TRES pratique, et TRES chiant à implémenter soi-même.

Donc acte.

>>> for x in set((3, 2, 2, 2, 1, 2)): # booooooo
...     print x
...
1
2
3
>>> for x in sset((3, 2, 2, 2, 1, 2)): # clap clap !
...     print x
...
3
2
1

Attention, c’est pas la structure de données la plus rapide du monde…

Je suis une feignasse et j’aime les one-liners sur les dicos

Je ne comprends pas pourquoi + ne fonctionne pas sur les dico.

>>> dmerge({"a": 1, "b": 2}, {"b": 2, "c": 3})
{'a': 1, 'c': 3, 'b': 2}

Ne modifie pas les dictionnaires originaux.

>>> from batbelt.structs import rename
>>> rename({"a": 1, "b": 2})
>>> rename({"a": 1, "b": 2}, 'b', 'z')
{u'a': 1, u'z': 2}

Modifie le dictionnaire original et n’est PAS thread safe.

Et le cas tordu mais tellement satisfaisant :

>>> from batbelt.structs import unpack
>>> dct = {'a': 2, 'b': 4, 'z': 42}
>>> a, b, c = unpack(dct, 'a', 'b', 'c', default=1)
>>> a
2
>>> b
4
>>> c
1

Slugifier

>>> slugify(u"Hélo Whorde")
helo-whorde

Il y a pas mal de réglages possibles avec slugify(), mais je vous laisse les découvrir :-) Cette fonction fait partie du sous-module strings, qui contient d’autres utilitaires comme escape_html/unescape_html (qui transforme les caractères spéciaux en HTML entities et inversement) ou json_dumps/json_loads (qui fait un dump / load du JSON en prenant en compte le type datetime).

Importer une classe ou une fonction depuis une string

Dès que vous faites un fichier de config vous avez besoin de ce genre de truc, mais la fonction __import__ a une signature uber-zarb. Voici une version beaucoup plus simple:

TaClasse = import_from_path('foo.bar.TaClasse')
ton_obj = TaClasse()

Capturer les prints

Parfois on a une lib qui print plutôt que de retourner une valeur. C’est très chiant. J’ai donc fait un context manager qui permet de récupérer tout ce qui est printé dans le block du with.

>>> with capture_ouput() as (stdout, stderr):
...    print "hello",
...
>>> print stdout.read()
hello

Créer un décorateur qui accepte des arguments

Même dans le cas où vous avez parfaitement compris les décorateurs grâce à un très bon tuto (^^), se souvenir de comment faire un décorateur qui attend des arguments en paramètre, c’est mission impossible. Voici donc un décorateur… pour créer un décorateur.

Étape un, écrire votre décorateur :

# tout les arguments après 'func' sont ceux que votre décorateur acceptera
@decorator_with_args()
def votre_decorateur(func, arg1, arg2=None):
 
    if arg1:
        # faire un truc
 
    # ici on fait juste le truc habituel des décorateurs
    # wrapper, appel de la fonction wrappée et retour du wrapper...
    def wrapper():
        # arg2 est dans une closure, on peut donc l'utiliser dans
        # la fonction appelée
        return func(arg2)
 
 
    return wrapper

Et on peut utiliser son décorateur tranquile-bilou :

@votre_decorateur(False, 1)
def hop(un_arg):
    # faire un truc dans la fonction décorée

Les processus parallèles finissent toujours par se rencontrer à l’infini et corrompre leurs données

Mais en attendant on en a quand même besoin. Parfois un petit worker, c’est sympa, pas besoin de faire compliqué et de sortir des libs de task queue complètes:

 
from batbelt.parallel import worker
 
@worker()
def une_tache(arg):
    # faire un truc avec arg
    arg = arg + 10
    return arg
 
 
# on demarre le worker
process = une_tache.start()
 
# on balance des tâches au worker
for x in range(10):
    process.put(x)
 
# on récupère les résultats (optionnel)
# ca peut être dans un fichier différent
for x in range(10):
    print process.get()
 
## 10
## 11
## 12
## 13
## 14
## 15
## 16
## 17
## 18
## 19
 
# on arrête le worker
process.stop()

Le worker est un subprocess par défaut, mais vous pouvez en faire un thread avec @worker(method=”tread”). Toujours utile, par exemple pour avec un processeur de mails qui envoit tous les mails hors du cycle requête / réponse de votre site Web. Par contre si votre process meurt la queue est perdue.

Template du pauvre

Avec format(), on a déjà un mini-langage de template intégré. Pas de boucle, mais pour des tâches simples ça suffit. Du coup j’ai une fonction render() qui prend un fichier de template au format string Python et qui écrit le résultat dans un autre. Pratique pour faire des fichiers de conf configurable.

from batbelt.strings import render
 
render('truc.conf.tpl', {"var": "value"}, "/etc/truc.conf")

Il y a aussi des implémentations de Singleton, du Null Pattern, etc. Mais ça s’utilise moins souvent alors je vais pas faire une tartine.

]]>
http://sametmax.com/batbelt-la-lib-des-petits-outils-python-qui-vont-bien/feed/ 15
Capturer l’affichage des prints d’un code Python 8 http://sametmax.com/capturer-laffichage-des-prints-dun-code-python/ http://sametmax.com/capturer-laffichage-des-prints-dun-code-python/#comments Sat, 29 Sep 2012 14:03:36 +0000 http://sametmax.com/?p=2343 Hier j’ai eu rencontré le travail d’une de ces fameuses personnes qui pensent que la ré-utilisabilité c’est pour les pédés, et qui font des scripts dont la moitié des infos renvoyées sont printées au milieu de blocs de code de 50 lignes, sans possibilité de les récupérer.

Heureusement, avec un petit hack, on peut capturer ce qu’affiche un autre code, et sauver le bébé, l’eau du bain, et même le canard en plastique.

Le code pour les gens pressés

J’ai enrobé l’astuce dans un context manager, ça rend l’utilisation plus simple.

import sys
from io import BytesIO
from contextlib import contextmanager
 
@contextmanager
def capture_ouput(stdout_to=None, stderr_to=None):
    try:
 
        stdout, stderr = sys.stdout, sys.stderr
        sys.stdout = c1 = stdout_to or BytesIO()
        sys.stderr = c2 = stderr_to or BytesIO()
        yield c1, c2
 
    finally:
 
        sys.stdout = stdout
        sys.stderr = stderr
 
        try:
            c1.flush()
            c1.seek(0)
        except (ValueError, IOError):
            pass
 
        try:
            c2.flush()
            c2.seek(0)
        except (ValueError, IOError):
            pass

Notez l’usage de yield.

Et ça s’utilise comme ça:

with capture_output() as stdout, stderr:
    fonction_qui_fait_que_printer_la_biatch()
 
print stdout.read() # on récupère le contenu des prints

Attention, le code n’est pas thread safe, c’est fait pour hacker un code crade, pas pour devenir une institution. Mais c’est fort pratique dans notre cas précis.

Comment ça marche ?

stdin (entrée standard), stdout (sortie standard) et stderr (sortie des erreurs) sont des file like objects, c’est à dire qu’ils implémentent l’interface d’un objet fichier: on peut les ouvrir, les lire, y écrire et les fermer avec des méthodes portant le même nom et acceptant les mêmes paramètres.

L’avantage d’avoir une interface commune, c’est qu’on peut du coup échanger un file like objet par un autre.

Par exemple on peut faire ceci:

import sys
log = open('/tmp/log', 'w')
sys.stdout = log # hop, on hijack la sortie standard
print "Hello"
log.close()

Comme print écrit dans stdout, en remplaçant stdout par un fichier, print va du coup écrire dans le fichier.

Mais ce code est fort dangereux, car il remplace stdout de manière définitive. Du coup, si du code print après, il va écrire dans le fichier, même les libs externes, car stdout est le même pour tout le monde dans le process Python courant.

Du coup, il est de bon ton de s’assurer la restauration de stdout à son état d’origine:

import sys
log = open('/tmp/log', 'w')
bak = sys.stdout # on sauvegarde l'ancien stdout
sys.stdout = log
print "Hello"
log.close()
sys.stdout = bak # on restore stdout

Comme je le disais plus haut, ceci n’est évidement pas thread safe, puisqu’entre la hijacking et la restoration de stdout, un autre thread peut faire un print.

Dans notre context manager, on utilise BytesIO() et non un fichier. BytesIO est un file like objet qui permet de récupérer un flux de bits en mémoire. Donc on fait écrire print dedans, ainsi on a tout ce qu’on affiche qui se sauvegarde en mémoire.

Bien entendu, vous pouvez créé vos propres file like objects, par exemple un objet qui affiche à l’écran ET capture la sortie. Par exemple, pour mitiger le problème de l’absence de thread safe: 99% des libs n’ont pas besoin du vrai stdout, juste d’un truc qui print.

import sys
from io import BytesIO
 
class PersistentStdout(object):
 
    old_stdout = sys.stdout
 
    def __init__(self):
        self.memory = BytesIO()
 
    def write(self, s):
        self.memory.write(s)
        self.old_stdout.write(s)
 
 
old_stdout = sys.stdout
sys.stdout = PersistentStdout()
 
print "test" # ceci est capturé et affiché
 
sys.stdout.memory.seek(0)
res = sys.stdout.memory.read()
 
sys.stdout = PersistentStdout.old_stdout
 
print res # résultat de la capture

Pour cette raison le code du context manager permet de passer le file like objet à utiliser en argument. On notera aussi que si on souhaite rediriger stdout mais pas stderr et vice-versa, il suffit de passer sys.stdout et sys.stderr en argument :-)

]]>
http://sametmax.com/capturer-laffichage-des-prints-dun-code-python/feed/ 8
Les context managers et le mot clé with en Python 13 http://sametmax.com/les-context-managers-et-le-mot-cle-with-en-python/ http://sametmax.com/les-context-managers-et-le-mot-cle-with-en-python/#comments Mon, 03 Sep 2012 17:56:43 +0000 http://sametmax.com/?p=1987 with est utilisé comme dans aucun autre langage en Python. Au premier abord mystérieux, il agit en fait comme les décorateurs en permettant d'exécuter du code automatiquement avant et après un autre code. Mais à l'image des décorateurs, tout ce qu'il fait pourrait être écrit à la main sans utiliser le mot clé with. Utiliser with est une question de style. ]]> Le mot clé with est utilisé comme dans aucun autre langage en Python. Au premier abord mystérieux, il agit en fait comme les décorateurs en permettant d’exécuter du code automatiquement avant et après un autre code. Mais à l’image des décorateurs, tout ce qu’il fait pourrait être écrit à la main sans utiliser le mot clé with. Utiliser with est une question de style.

Supposons que vous vouliez afficher quelque chose avant un bout de code, et après un bout de code, même si celui-ci rate. Vous feriez quelque chose comme ça:

def truc():
    print "machin"
 
print "Avant"
try:
    truc()
finally:
    print "Après"

Et ça va afficher:

Avant
machin
Après

Et avec:

def truc():
    print "machin"
    raise Exception('Fail !')

‘Après’ sera quand même affiché. Ça plantera, mais la dernière action sera toujours faite.

Si vous le faites souvent, vous voudrez factoriser du code. Un des moyens de le faire est d’utiliser les context managers.

Créer son propre context manager

Un context manager est une classe ordinaire en Python. Sa seule spécificité est de déclarer une méthode __enter__() et une méthode __exit__(). Ces méthodes sont des méthodes ordinaires, leur nom spécial est juste là par convention, et en les nommant ainsi on s’assure qu’elles seront détectées et utilisées automatiquement.

Notre code là haut peut donc se réécrire ainsi:

class MonSuperContextManager(object):
    def __enter__(self):
        print "Avant"
    def __exit__(self, type, value, traceback):
        # faites pas attention aux paramètres, ce sont toutes les infos
        # automatiquement passées à __exit__ et qui servent pour inspecter
        # une éventuelle exception
        print "Après"
 
with MonSuperContextManager():
    truc()

L’avantage de with est multiple:

  • Il permet de visualiser très précisément où on entre dans l’action et où on en sort (c’est un seul block)
  • Il permet de réutiliser les actions faite à l’entrée et à la sortie de l’action.
  • Même si une exception est levée, l’action de sortie sera exécutée juste avant le plantage. __exit__ est en effet garantie d’être appelée quoiqu’il arrive. Bon, évidement, si il y a une coupure de courant…

En gros, créer un context manager, c’est faire un raccourci lisible pour try/finally. Point.

Un exemple utile de context manager

Supposons que vous ayez beaucoup de travail à faire dans plein de dossiers. Vous voulez vous assurer que vous allez dans le dossier de travail, puis que vous retournez au dossier initial à chaque fois.

import os
 
class Cd(objet):
    def __init__(dirname):
        self.dirname = dirname
    def __enter__(self):
        self.curdir = os.getcwd()
        os.chdir(self.dirname)
    def __exit__(self, type, value, traceback):
        os.chdir(self.curdir)

On l’utilise comme ça:

# ici on est dans /home/moi
 
with Cd('/'):
 
    # faire un truc dans /
 
    with Cd('/opt'):
 
        # faire un truc dans /opt
 
    # ici on est dans /
 
# ici on est dans /home/moi

C’est d’ailleurs ce que fait fabric.

Le mot clé as

Tout ce qu’on retourne dans __enter__ peut être récupéré grâce au mot clé as. Imaginons un context manager qui permette d’ouvrir un fichier et de le fermer automatiquement:

class OpenFile(objet):
    def __init__(filename, mode='r'):
        self.filename = filename
        self.mode = mode
    def __enter__(self):
        self.file = open(self.filename, self.mode)
        # ici on retourne l'objet fichier, il sera accessible avec "as"
        return self.file
    def __exit__(self, type, value, traceback):
        self.file.close()

On l’utilise comme ceci:

with OpenFile('/etc/fstab') as f:
    for line in f:
        print line

f va contenir ici l’objet fichier, car nous l’avons retourné dans __enter__. A la fin du bloc with, le fichier sera fermé automatiquement.

Et devinez quoi, Python possède déjà un context manager qui fait ça:.

with open(vot_fichier_msieu_dames) as f:
   # faire un truc

Context managers sous forme de fonctions

Faire les choses sous forme de classes, c’est pratique quand on a beaucoup de logique à encapsuler. Mais la plupart des context managers sont très simples. Pour cette raison, Python vient avec plein d’outils pour se simplifier la vie avec with dans un module judicieusement nommé contextlib.

Pour l’utiliser, il faut avoir des notions sur les décorateurs, et le mot clé yield. Si ce n’est pas votre cas, restez sur la version sous forme de classe :-)

Supposons que l’on veuille recréer le context manager open:

from contextlib import contextmanager
 
@contextmanager
def open(filename, mode):
    try:
        f = open(filename, mode)
        yield f
    finally:
        f.close()

Bon, c’est simplifié, hein, le vrai est plus robuste que ça.

Comment ça marche ?

D’abord, on utilise le décorateur @contextmanager pour dire à Python que la fonction sera un context manager.

Ensuite, on fait un try/finally (il est pas automatique comme avec __enter__ et __exit__).

yield sépare le code en deux: tout ce qui est avant est l’équivalent de __enter__, tout ce qui est après est l’équivalent de __exit__. Ce qui est “yieldé” est ce que l’on récupère avec le mot clé as.

Context manager et décorateur, le shampoing deux en un

Ces deux fonctionnalités se ressemblent beaucoup: elles permettent toutes les deux de lancer du code automatiquement avant et après un code tiers. La seule différence est que le context manager le fait à la demande, alors que le décorateur s’applique à la définition d’une fonction.

Quand on sait comment ils marchent, il est facile de faire un context manager utilisable également en tant que décorateur.

from functools import wraps
 
class ContextDecorator(object):
    # __call__ est une méthode magique appelée quand on utilise () sur un objet
    def __call__(self, f):
        # bon, cette partie là suppose que vous savez comment marche un
        # décorateur, si c'est pas le cas, retournez lire l'article sur S&M
        # linké dans le premier paragraphe
        @wraps(f)
        def decorated(*args, **kwds):
            # notez le with appelé sur soi-même, c'est y pas mignon !
            with self:
                return f(*args, **kwds)
        return decorated

Et voilà, il suffit d’hériter de ça, et on a un décorateur + context manager. Par exemple, si on veut timer un truc:

import datetime
 
class TimeIt(ContextDecorator):
 
    def __enter__(self):
        self.start = datetime.datetime.now()
        print self.start
 
    def __exit__(self, type, value, traceback):
        print (datetime.datetime.now() -self.start).total_seconds()

Timer juste un appel:

def foo():
    # faire un truc
 
with TimeIt():
    foo()

Timer tous les appels:

@TimeIt()
def foo():
   # faire un truc

Notez que ContextDecorator est présent par défaut dans le module contextlib sous Python 3.2.

]]>
http://sametmax.com/les-context-managers-et-le-mot-cle-with-en-python/feed/ 13
J’adore les context managers Python http://sametmax.com/jadore-les-context-managers-python/ http://sametmax.com/jadore-les-context-managers-python/#comments Tue, 24 Jul 2012 15:15:53 +0000 http://sametmax.com/?p=1296 En Python il y a plein de trucs pas du tout indispensables: les metaclasses, les décorateurs, les listes en intentions… Elles sont là uniquement pour rendre le langage plus agréable à l’usage, mais on pourrait faire sans. Les context managers en font partie. Loin de moi l’idée de faire un tuto ce matin sur les context managers, mais j’ai juste l’envie d’énoncer tout haut mon amour cette fonctionnalité.

Prenez un model Django Media: vous transcodez ce media, vous faites souvent des opérations dans son dossier. Il faut s’assurer que ça marche. Et quand ça foire, il faut s’assurer que l’on a bien nettoyé le répertoire de travail.

Hop:

class Media(models.Model):
 
    [...]
 
    @contextmanager
    def in_dir(self, delete_on_error=False):
        # si vous ne comprenez pas ce code, ne vous en faites pas,
        # c'est du Python avancé
        try:
            os.makedirs(self.dir)
        except (OSError, IOError) as e:
            pass
 
        try:
            yield self.dir
        except:
            if delete_on_error:
                shutil.rmtree(self.dir)
            raise

Et voilà, Max dans ses scripts de transcoding magiques n’a pas à se poser de question. Il a juste à faire:

with media.in_dir(delete_on_error=True) as d:
    # faire des trucs avec le dossier dont le chemin est dans "d"

Le chemin spécifique pour ce média est garanti d’exister.

Si il y a une exception, le dossier est garanti d’être nettoyé.

C’est simple, c’est propre, c’est beau. J’aime.

]]>
http://sametmax.com/jadore-les-context-managers-python/feed/ 0