Site original : Sam & Max: Python, Django, Git et du cul
Je n’ai jamais eu besoin d’un type exactement comme les d’Enum en Python de ma vie. Jamais eu le cas d’utilisation. Alors quand la feature est arrivé en python 3.4, cela a été un non-évènement pour moi.
Pourquoi faire ?
Je comprends parfaitement que des langages comme le C ou le Java en aient besoin. Leur syntaxe amène à ce genre de chose. Leur typage aussi. Mais Python ? Il y a tellement de manières de représenter les données, de les manipuler, de les passer.
Les enums c’étaient vraiment pour la déco à mes yeux.
Mais la fonctionnalité a fait débat. Si, si, on peut devenir tout rouge à propos d’une séquence de constantes.
Regardons un peu plus près de quoi il est question.
D’abord, en Python, on définit généralement les constantes ainsi :
FOO = 1 BAR = 2 DOH = 3 OYO = 4 |
Ce ne sont pas vraiment des constantes. Il n’y a rien qui ne puisse être réassigné en Python, même si parfois ça demande un peu de bidouillage, mais c’est une convention que les gens respectent.
Comme les modules sont des singleton, on peut facilement faire:
import module print(module.FOO) |
Parfois on a besoin d’un peu plus. On veut plusieurs groupes de constantes. Alors on peut utiliser une classe.
class Stuff: FOO = 1 BAR = 2 DOH = 3 OYO = 4 |
Et on peut faire:
from module import Stuff print(Stuff.FOO) |
Après, il y a toujours les variantes des gens qui veulent itérer dessus.
from module import Stuff for attr, value in Stuff.__dict__.items(): print(attr, value) |
Et puis il y a les gens qui veulent itérer dessus de manière ordonnée. On leur conseille généralement d’utiliser namedtuple
:
from collections import namedtuple Stuff = namedtuple('Stuff', 'FOO BAR DOH OYO')(*range(1, 5)) for val in Stuff: print(val) |
Mais certains vont vouloir les noms avec les valeurs. Ils se tourneront peut être vers OrderedDict
:
from collections import OrderedDict Stuff = OrderedDict((('FOO', 1), ('BAR', 2), ('DOH', 3), ('OYO', 4))) for attr, value in Stuff.items(): print(attr, value) |
Mais pas d’accès direct par attribut, et il faut faire gaffe à la saisie ou quand on change les valeurs, etc.
Bref, les quelques personnes qui voulaient les enums n’étaient parfaitement satisfaites de ces solutions.
En gros, les Enums sont des classes bridées qui représentent des itérables de constantes. Chaque constante est un couple clé/valeur.
from enum import Enum class Stuff(Enum): FOO = 1 BAR = 2 DOH = 3 OYO = 4 for member in Stuff: print(member.name, member.value) |
Les enums peuvent être héritées (mais de manière limitée) et implémenter un peu de logique puisque ce sont finalement des classes.
Il existe un type IntEnum
pour que chaque membre soit comparable à un int directement plutôt que de voir chercher .value
, qui par ailleurs peut être n’importe quel type avec Enum
.
Et on a une syntaxe raccourcie:
Stuff = Enum('Stuff', 'FOO BAR DOH OYO') |
Bref, la feature ne fait pas grand-chose, en tout cas rien de ce qu’on ne pouvait faire avant. Mais elle a quelques avantages :
Mais tout ce temps après avoir vu les enums introduites, je n’en ai toujours pas l’usage. Jamais assez de constantes, ou alors quand j’en ai, je peux en faire des tuples ou des dicos sans avoir besoin d’un import.
Je suppose que je fais pas assez d’extensions C.
Quoiqu’il en soit généralement quand je vois des gens demander des enums, ce sont souvent pour de mauvaises raisons, pour reproduire ce qu’ils ont l’habitude d’utiliser dans un autre langage. Mais il y a vraiment peu de use cases typiquement Python pour les enums.
Allez, si vous avez quelques flags à mettre quelque part ou une API à wrapper, mettre une enum ne fera pas de mal.
Dans la config par défaut, les roles postgres matchent les utilisateurs système sous Linux. Au départ, le seul super utilisateur est “postgres”, et à chaque fois qu’on veut lancer psql
pour faire de l’admin il faut se connecter avec ce user.
Pour se simplifier la vie en dev:
# on se log come "postgres" sudo -i -u postgres # sous les debian likes. sinon "su" marche aussi # on créé un utilisateur createuser --interactive # et on dit "oui" quand il demande de créer un super user # on créé une db à son nom createdb ton_username |
Et voilà, on peut utiliser psql
depuis son compte.
Ça m’était déjà arrivé plusieurs fois après avoir ajouté mon env virtuel et ma branch git dans mon prompt : soudainement il se met à faire n’importe quoi. Des mixes de caractères, des sauts de ligne qui se font pas, la fusion de la ligne de commande sur elle-même.
Inutilisable.
Aujourd’hui plutôt que de subir le problème, j’ai cherché une solution, et pouf : quand on utilise des couleurs dans son prompt, il faut entourer tout le balisage de []
.
Mais le balisage qui colore le prompt est composé de séquences d’échappements, qui contiennent aussi []
, donc il faut mettre des anti-slash.
Du coup on passe de ça :
export PS1="\[\033[01;34m\]\$(basename '$VIRTUAL_ENV')\e[0m $PS1"
à :
export PS1="\[\033[01;34m\]\$(basename '$VIRTUAL_ENV')[\e[0m] $PS1"
à la solution qui marche :
export PS1="\[\033[01;34m\]\$(basename '$VIRTUAL_ENV')\[\e[0m\] $PS1"
Évidemment à appliquer à tous les codes d’échappement chelou qu’on a réparti un peu partout.
J’ai un tampon prêt à tirer pour le premier qui me parle de zsh.
En Python, les threads et l’asyncio s’utilisent ensemble
Je vois beaucoup dans les tutos ici et là des gens qui opposent asyncio avec l’ancienne manière de faire de l’IO non bloquante : les threads.
C’est une erreur : les deux méthodes ne sont pas opposées, elles sont complémentaires.
En effet, asyncio ne peut être non bloquant que sur le réseau : ça ne gère pas l’IO sur les fichiers. Par ailleurs, toute opération CPU bloque également la boucle d’évènement.
C’est très bien de ne pas bloquer sur le réseau, mais encore faut-il pouvoir faire la requête réseau.
Si votre programme est bloqué à attendre une compression zip qui dure 3 secondes, pendant ce temps, les opérations réseaux déjà lancées tournent bien.
MAIS IL N’EN LANCERA PAS DE NOUVELLES.
Pendant 3 secondes, aucune nouvelle requête ne sera faite puisque le programme ne fait qu’une chose : ziper. Ca marche pour d’autres choses hein : copie de fichier, gros calcul matheux, traverser une grande liste, etc.
Donc si vous avez fini toutes vos requêtes à la seconde 1, pendant 2 secondes, votre programme n’est pas utilisé à son plein potentiel.
Les threads ne permettent pas d’avancer plus vite, mais ils permettent de faire 2 travaux en parallèle. Par exemple, de lancer pendant ces 2 secondes des requêtes supplémentaires sur le réseau.
On a vendu asyncio comme plus léger que les threads. Ce n’est pas tout à fait exact. asyncio a les avantages suivants :
Mais si on a quelques threads, le changement de contexte entre les threads est moins important que ce que coûte asyncio à faire tourner.
Pour certains travaux où le coût d’une opération réseau est faible, mais que cumulativement toutes les opérations ralentissent votre programme, un thread sera plus performant.
Par exemple, beaucoup d’opérations sur les bases de données tombent dans cette catégorie, à moins d’avoir des très longues requêtes.
Dans ce cas, avoir un thread dédié aux opérations de la base de données peut être une bonne décision.
Si vous avez des opérations sur des fichiers ou de grosses opérations CPU, les faire travailler dans un thread peut booster votre programme. Ca tombe bien il y a une lib pour ça.
Si vous avez beaucoup de petites opérations réseau, les grouper, dans un thread à part, peut booster votre programme.
Et asyncio pour le reste, par exemple des requêtes HTTPs, DNS, SMTP, FTP, SSH, etc.
On peut donc copieusement utiliser les deux en parallèle. La bonne nouvelle, c’est que Guido a designé asyncio pour ça:
import asyncio from concurrent.futures import ThreadPoolExecutor async def main(loop, executor): # ça c’est fait avec asyncio await aiohttp.get('http://bidule.com') # ça c’est fait dans un thread await loop.run_in_executor(None, gros_calcul, params) loop = asyncio.get_event_loop() loop.run_until_complete(main(loop)) |
Notez bien :
ProcessPoolExecutor
ou un ThreadPoolExecutor
pour choisir la stratégie de parallélisme à adapter : process, thread, nombres de workers, etc.Bien entendu, chaque fois que vous ajoutez un mécanisme de concurrence, vous ajoutez de la complexité, donc ne le faites que si c’est nécessaire.
Commencez avec un programme synchrone simple. Si c’est trop lent, ajouter les threads ou l’asyncio, si ça ne suffit pas, utilisez les deux.
Vous savez, quand on ne brule pas un Troll, ses blessures se soignent rapidement, et il attaque à nouveau.
Et vous savez également comme j’aime troller JS.
De plus, il y a quelque temps, je vous affirmais que NodeJS n’était pas mature.
Est-ce que l’écosystème JS a muri depuis ?
Et bien maintenant je crois qu’on a passé l’enfance, et qu’on est dans la phase de l’adolescence. Ca cri, ça bouge, ça a plein d’énergie, et ça mérite des baffes.
Je sais, je sais, je vais encore avoir une horde de fans boys qui aiment le typage mou et les champs de moustaches venir me dire que je devrais arrêter d’écrire, de coder et m’enfoncer la version imprimée de Wikipédia dans l’anus.
Mais dans l’histoire de JS, y a pas eu un moment qui ne méritait pas un bon article de ce genre. A croire que c’est volontaire.
En l’occurrence, j’ai eu envie d’écrire cet article à la suite de plusieurs évènements successifs:
it’s F/OSS, but I’d strongly suggest learning from Persona’s design rather than directly re-hosting the code
Donc, reprenons.
NodeJS est toujours splitté entre 3 versions : Node, Io.JS et convergence.
Il y a plus de frameworks front end que jamais : Angular, React, Amber, Backbone, Polymer, ExtJS, Aurelia, Durandal, Knockout, Mithril, MarionetteJS, Vue.js, Meteor, etc. Et je ne liste que les plus connus, car la liste est interminable.
Ils ont tous des versions différentes, des addons (qui ont des versions et des dépendances), des docs et tutos répartis un peu partout..
Par dessus ça, on rajoute les langages qui compilent vers du JS qui eux aussi se multiplient comme les gremlins mouillés: Coffeescript, Dart, GWT, Closure, Haxe, Elm, TypeScript, Brython, Skulpt, PyJS, etc. Vous voulez, un listing complet ? Attention ça pique les yeux.
En clair, la seule chose que les gens aiment autant que de pallier l’absence de library standard de JS est de pallier la syntaxe de JS.
Mais on ne peut pas blâmer uniquement le langage. La communauté JS a pris la modularité tellement à l’extrême qu’aucune solution standard ne s’est démarquée pour rien : outils de build, libs de tests, solutions de routing, toolings fonctionnels, et même packages managers…
Même à l’intérieur d’un écosystème, c’est les poupées russes. Par exemple React peut être accompagné d’un pattern que Facebook appelle Flux. C’est du MVC monodirectionnel. Les modèles sont immutables. Le contrôleur est à base d’évènements. Les vues sont gérées par des widgets React. Rien de fou, mais ça faisait pas assez innovant alors ils ont inventé un nouveau nom.
Bref, ils ont leur propre implémentation, mais même la référence n’a pas gagné la guerre. Non, en JS, ça a déclenché immédiatement un concours de celui qui pisse le plus loin et vous pouvez (devez ?) choisir entre : Flux, redux, alt, reflux, flummox, fluxible, fluxxor, marty.js, fynx, MacFly, DeLorean.js, fluxify, fluxury, exim, fluxtore, Redx, fluxx. Les noms ne sont pas une tentative d’humour de ma part.
Ce qui donne les comments du genre :
What a wonderful example of the paradox of choice!
No wonder that it’s easier to get started with Meteor + React, than with Flux + React.
Pour rappel, Meteor est le truc le plus expérimental qui existe en termes de stack techno à l’heure actuelle.
Évidemment tous ces projets ont une documentation succincte, une durée de vie aléatoire, et on peut déjà lire des trucs comme :
Flummox 4.0 will likely be the last major release. Use Redux instead.
Et ils ne sont pas compatibles entre eux. Et en fait ils ne font pas exactement la même chose :
how does redux “replace” flummox, exactly? Seems like they have two very different approaches
Je vous mets les comments car je sais que vous n’avez pas le temps d’aller lire, et encore moins d’étudier chacun de ses projets pour décider duquel utiliser, déjà qu’il faut apprendre React… A vous ne saviez pas ce qu’était React ? Mais vous êtes à la bourre dites donc !
Rassurez-vous, choisir ce genre de techno a seulement un impact sur tout le code front de votre projet. C’est tout.
Mais les outils s’améliorent, et JS est de plus en plus facile à coder. Pas vrai ? Pas vrai ?
Nope.
React fait ramer misérablement la console de debug de Firefox.
Les préprocesseurs ont tout envahi, et si vous choisissez 4 libs, l’un sera en ES6, l’autre en typescript et la troisième en Dart. Et il faut un setup de connard pour debug du code de préprocesseur confortablement : détecteur de changement, builder, injecteur de code, source mapper…
Les frameworks comme Angular ou React ont une gestion d’erreur bien à eux, qui affichent des messages chelous.
L’installation est devenue un enfer. Entre les dépendances dépréciées, les libs incompatibles, les différents outils de build, et les options de config et les plugins, c’est une merde incommensurable. Plusieurs standards d’installeurs (et outils joints) se tirent la bourre : AMD, CommonJS et Harmony. Vous vous souvenez du temps ou on copiait juste jQuery dans le répertoire static ?
Attendez, avec Webassembly on pourra même plus faire “read source”.
Donc JS…
On peut faire plus de choses qu’avant. De manière plus propre (enfin propre, on parle de JS là…).
Mais l’écosystème, c’est le bordel général, l’anarchie totale, et ça continue d’avancer, en laissant tout le reste derrière.
Les projets JS s’accumulent, de freelances en SS2I qui se pointent, codent ou (surtout) recodent, et repartent en laissant un projet qu’il sera imbuvable à reprendre, déprécié avant même d’être terminé, non documenté, non testé.
Un projet jetable.