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
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.
Y’a des astuces sympa. J’aime bien l’implémentation de first() par exemple, il fallait y penser.
J’ai un commentaire, ça ne serait pas plus simple pour l’utilisateur de rajouter des arguments avec des valeurs par défaut (genre unidecode=True/False et ascii_only=True/False) plutôt que d’avoir 3 fonctions slugify ?
Personnellement, je trouve get, attr “syntactiquement étranges” (si j’ose dire). Tous les paramètres sont sur le même plan: la structure de données, les éléments du “chemin” dans la structure et la valeur par défault.
Un truc comment ça me paraît déjà moins étrange:
idem pour unpack: je passerais un tuple contenant les clés à unpacker.
Je suppose que t’as cherché pourquoi. En ce qui me concerne, je trouve cette raison convaincante:
@kontre:
Il n’y a que vraiment qu’une fonction slugify, et elle s’appelle “
slugify(
)”.Elle est très facile à utiliser car un help dessus te montre qu’elle n’a que deux arguments, et tu sais ce que tu peux faire avec sans regarder la doc de plus prêt.
Les 3 sous fonctions slugify ne sont pas là pour l’usage courant.
unicodedata_slugify
etunidecode_slugify
ne seront normalement jamais appelée directement. La meilleure est automatiquement aliasée comme “slugify()
” selon l’existence de la libunidecode
ou non, et si elle existe, il n’y a pas de raison de ne pas l’utiliser. Au cas où, l’implémentation sansunidecode
est laissée utilisable, mais mettre une condition n’a pas de sens pour l’utilisateur sauf exception improbable.Reste
unicode_slugify
, qui est aussi un cas particulier. On voudra rarement l’utiliser, donc je ne voulais pas ajouter une argument pour ça : les utilisateurs regarderait la signature et se demanderait “à quoi ça sert” ? J’ai donc choisi de simplifier au max l’utilisation de l’API le plus usité, et j’ai planqué dans les source les cas particuliers. C’est très important pour l’appropriation d’un API.@Etienne:
tu peux:
C’est la beauté de Python.
Quand au plus du dictionnaire, on a déjà
dict.update()
qui a un comportement bien défini. Pourquoi le critique s’applique à+
et pas àupdate()
. Et si update est parfaitement ok, alors il n’y a pas de raison que les gens ne s’attendent pas à la même chose avec plus. Franchement, tu t’attends à autre chose toi ? C’est le résultat qui me parait le plus naturel, et je ne vois pas comment ça peut introduire un bug. Additionner des dicos arrive pas souvent le jour où le mec en a besoin, il va de toute façon faire un test dans le shell et voir ce que ça donne.@Sam
J’y avais jamais réfléchi jusque il y a 10 minutes, mais il me semble que
.update()
exprime bien ce qui se passe: mise à jour du dictionnaire depuis un autre dictionnaire: celui qui met à jour a la priorité sur celui qui est mis à jour. Comme je le comprend, les clés existantes sont mise à jour et créées si elles n’existent pas.Dans le cas du + tu ajoutes quelque chose. D’ailleurs, dans le cas d’une liste, le + se traduit par
.extend()
, qui est plus proche de quelque chose comme la concaténation que de la mise à jour.Sinon:
Oui, évidemment, mais ce que je disais concerne la signature de la fonction. Mais bon, c’est un détail.
@Sam OK, ça se comprend même si perso j’aime moins. Des goûts et des couleurs…
Par contre ils sont où les
from __future__ import ...
? Allez, python3 et qu’ça saute !Les PL sont les bienvenus.
PL = ? (Parfaitement Loufoques? Petits Loulous? Parfois Lourds? Premiers Largués? Pisses Lent? …)
Pull request. Quand tu fork un repo git, tu peux proposer à l’autre de merger ton code avec un pull de sa part, cette requête est très facilitée sous Github car on peut le faire en un clic.
PL ?
j’ai bien pensé à picolitre, mais avec ca on va pas picoler grand’chose ;)
Uhu, j’ai survolé le bug du timestamp, ça bashe bien ! On dirait que l’opensource manque parfois de décideurs. Mais que fait BDFL ?
J’avais deviné que tu parlais de pull request, mais pourquoi PL et pas PR ? Je ferai peut-être quelques trucs pour le fun (et pour apprendre, j’ai encore jamais fait de pull request), mais comme je ne pense pas que j’utiliserai votre lib (ça ne correspond pas à ce que je fais en ce moment) ça limite la motivation…
Je l’ai d’ailleurs pas encore dit, mais c’est cool de partager du code comme ça !
Question : il sort d’où le nom de la lib ?
Ahahahaha. Je voulais dire PR. Je sais pas pourquoi j’ai dis PL, en insistant en plus. Je suis un boulet.
Le nom de la lib: Bat belt. Nananananananna !
Pour le coup du timestamp il y a ça (pas très intuitif certes): http://stackoverflow.com/questions/2775864/python-datetime-to-unix-timestamp/2775982#2775982
Et pour le set ordonné, sur l’exemple donné je ne vois pas trop l’intérêt par rapport à un
sorted(set([...]))
?Le sorted set conserve l’ordre .
Gaffe au timestamp, je me suis fait avoir une fois.
>>> datetime.fromtimestamp(0)
datetime.datetime(1970, 1, 1, 1, 0)
>>> datetime.utcfromtimestamp(0)
datetime.datetime(1970, 1, 1, 0, 0)
Oui les dates ça fait toujours chier ><
De la même manière j'utilise plutôt
calendar.timegm(dt.utctimetuple())
Pour convertir en timestamp, j’ai déjà eu des cas foireux à cause du tz.