PROJET AUTOBLOG


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

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

⇐ retour index

La débâcle de async en 3.7 24   Recently updated !

mardi 7 août 2018 à 15:14

Quand les nouveaux mots clés async et await ont été introduits en Python 3.5, tout le monde a trouvé l’idée formidable. D’ailleurs, ça a été intégré à JavaScript.

Malheureusement, introduire des mots clés dans un langage est une opération très délicate.

Limites et contournements des mots clés

En Python les mots clés ont une caractéristique importante : on ne peut pas les utiliser pour quoi que ce soit d’autre.

Par exemple, class est un mot clé, donc je ne peux pas créer une variable, un attribut, ou une fonction appelé class. Ceci lève une erreur:

>>> class = 1
  File "", line 1
    class = 1
          ^
SyntaxError: invalid syntax
>>> class Foo: pass
... 
>>> Foo.class = 1
  File "", line 1
    Foo.class = 1
            ^
SyntaxError: invalid syntax
>>> 

Pour cette raison, quand on veut qu’une variable contienne une classe en Python, on la nomme cls:

>>> class Bar:
...     @classmethod
...     def wololo(cls):
...         print(cls, 'wololo')
... 
>>> 
>>> Bar.wololo()
 wololo
>>> 

C’est aussi pour cela que vous voyez parfois des variables nommées truc_. Souvent from_ par exemple, parce que from est un mot clé.

(pro tip: plutôt que from et to, utilisez src et dest)

Quand en Python 2 on a introduit True et False, un gros problème s’ensuivit: soit on en faisait des mots clés, et on pétait tout le code précédent qui utilisait ces mots, soit on en faisait des variables globales.

Le choix a été de garder la stabilité jusqu’à la prochaine version majeure, et c’est pour cela que:

Pour la 3.5, on avait donc ce même problème, avec une cerise sur le gâteau: la lib standard utilisait elle-même la fonction asyncio.async.

Le choix a donc de faire de async / await des variables globales, et de les transformer en mot clé en 3.7.

En 3.6, un warning a été ajouté pour rappeler aux gens de migrer leur code.

C’est un sacré taf, et ça comporte des risques comme nous allons le voir plus loin. C’est pour cette raison que l’ajout d’un mot clé dans Python est une des choses les plus difficiles à faire passer sur la mailling list python-idea.

Arrive la 3.7

La 3.7 est sortie avec tout un tas de goodies. Youpi. Mais aussi avec le passage de async/await de variables globales à mots clés, cassant la compatibilité ascendante. Quelque chose de rare en Python, et que personnellement j’aurais réservé pour Python 4, ne serait-ce que pour respecter semver.

Le résultat, tout un tas de systèmes ont pété: des linux en rolling release, des gens qui ont fait l’update de Python à la main, des gens qui maintiennent des libs compatibles 3.5 a 3.7…

D’autant que la 3.5 a asyncio.async, mais 3.7 considère ça une erreur.

Petit exemple avec l’impact sur debian.

Comment on aurait pu éviter ce merdier ?

D’abord, il aurait fallu ne pas introduire asyncio à l’arrache. Dans mon “au revoir” à Guido, je disais que je trouvais que les dernières fonctionnalités majeures de Python avaient été mises en oeuvre de manière précipitée.

Cela se vérifie encore et encore avec asyncio, dont il faudra que je fasse un article pour dire tout ce qui a mal tourné.

Casser la compatibilité ascendante dans une version mineure n’est pas acceptable, même si les dégâts sont limités et qu’on y survivra très bien.

Le fait qu’asyncio soit une API marquée comme “provisional” n’a jamais empêché quelqu’un d’appeler ses variables async. Après tout on utilise les threads depuis bien longtemps.

L’autre problème vient de l’amateurisme qui se glisse de plus en plus dans le dev.

C’est une bonne chose, parce que ça veut dire que la programmation est de plus en plus accessible et accueille de plus en plus de monde.

Mais cela veut dire aussi qu’une grosse part la population de programmeurs est aujourd’hui constituée de personnes qui n’ont ni les connaissances, compétences ou ressources pour faire les choses correctement.

On le voit particulièrement dans le monde JavaScript, ou c’est l’explosion (là encore, ça mérite un nouvel article). Mais l’exemple de la 3.7 nous montre que la communauté Python n’est pas immunisée, et je pense que le problème va s’amplifier.

Que veux-je dire par là ?

Et bien il y a 30 ans, cela ne serait pas venu à l’esprit de la plupart des devs de compiler quelques choses sans mettre les flags en mode parano pour voir ce qui allait péter. Après tout, quand on code en C, on sait que tout peut imploser à tout moment, alors la prudence est une question de culture.

Aujourd’hui par contre, la majorité des devs des langages haut niveau écrivent du code, font quelques tests à la main, et publient ça. D’autres les utilisent. Font des mises à jour en masse. Aucun ne prennent le temps ne serait-ce que d’activer les warnings les plus basiques.

Comme tout est facile à première vue, et c’est quelque chose dont on fait la promotion pédagogiquement parlant, car ça incite les gens à se lancer, on oublie la complexité inhérente à la programmation.

Mais il y a une différence colossale entre avoir un code qui marche une fois sur sa machine, et un code prêt pour la production.

Par exemple en Python, vous pouvez demander l’activation des warning pour chaque appel avec:

python -Wd

En 3.6, ça implique ceci:

>>> def async():
...     pass
... 
:1: DeprecationWarning: 'async' and 'await' will become reserved keywords in Python 3.7

L’info a toujours été là. Prête à être utilisée.

Mais alors pourquoi ne pas afficher tous les warnings, tout le temps ?

Et bien si je le fais:

python -Wa

Voilà ce que ça donne quand je lance juste le shell de python 3.6:

Voir le code sur 0bin.

Vous comprenez donc bien que ce n’est PAS activé par défaut. En fait, originalement le message était dans le corps de l’article, mais j’ai du le mettre sur 0bin parce que ça faisait planter WordPress. Si.

A chaque upgrade, il est important de vérifier les warnings pour préparer ses migrations futures.

Oui, c’est du boulot.

En fait…

La programmation, c’est BEAUCOUP de boulot

Même si on arrive maintenant à extraire une frame vidéo en gif en une ligne de commande.

Surtout maintenant qu’on y arrive en fait, car on multiplie les agencements hétérogènes de boites noires pour créer nos merveilleux programmes qui font le café.

Alors on prend des raccourcis.

Et puis aussi, parce qu’on ne sait pas. Qui parmi les lecteurs du blog, pourtant du coup appartenant à la toute petite bulle des gens très intéressés par la technique, connaissaient le rôle des warnings et comment les activer ?

Mais ce n’est pas le seul problème. Il y a clairement une question d’attentes et de moyen.

L’utilisateur (ou le client) final veut toujours plus, pour moins cher, et plus vite !

Et le programmeur veut se faire chier le moins possible.

Comme la complexité des empilements d’abstractions augmente, cela conduit à ignorer ce sur quoi on se base pour créer ce qui doit combler notre satisfaction immédiate.

J’ai parlé d’amateurs plus haut.

Mais je ne parle pas simplement de mes élèves. De mes lecteurs.

Je parle aussi de moi.

Prenez 0bin par exemple.

Il n’est plus à jour. Il n’a pas de tests unitaires. Il a des bugs ouverts depuis des années.

Ce n’est pas pro du tout.

Sauf que je ne suis pas payé pour m’en occuper, et c’est bien une partie du problème: nous sommes de nombreux bénévoles à faire tourner la machine a produire du logiciel aujourd’hui. Donc si je n’ai pas envie, fuck it !

Vous imaginez si l’industrie du bâtiment ou celle de l’automobile tournaient sur les mêmes principes ?

La moitié des dessins industriels faits par des bloggers, des étudiants, des retraités, des profs de lycées, des géographes, de biologistes et des postes administratifs ?

Des immeubles et des voitures dont des pièces sont fabriquées par des potes qui chattent sur IRC et s’en occupent quand ils ont le temps ? Gratuitement. Y compris le service après-vente.

Alors que les usagers veulent toujours plus: des normes sismiques et de la conduite autonome. Tout le monde le fait, alors la maison de campagne et la fiat punto, c’est mort, personne ne l’utilisera.

Difficile de maintenir la qualité à cette échelle.

Il y a tellement de demandes de dev, jamais assez d’offres, de ressources toujours limitées.

Et ça grossit. Ça grossit !

Aides techniques

Ceci dit, à l’échelle de la PSF, ça aurait dû être évité.

Avant d’aborder les aides techniques, il serait bon d’arrêter les conneries. Je me répète, mais c’était une vaste dauberie de faire passer async/await en mot clé avant Python 4.

J’ai parfaitement conscience du besoin de faire progresser un langage pour ne pas rester coincé dans le passé. Je suis pour async/await, très bonne idée, superbe ajout. Mettre un warning ? Parfait ! Mais on respecte semver s’il vous plait. Si vous avez envie de faciliter la transition, mettre un import __future__, et inciter les linters à faire leur taff.

En attendant, pour la suite, Python va faciliter le debuggage.

Par exemple, depuis la 3.7, les DeprecationWarning sont activés par défaut au moins dans le module __main__. Donc un développeur verra ses conneries bien plus rapidement.

E.G:

Imp est déprécié en 3.6, mais sans -Wd, on ne le voit pas:

$ python3.6
Python 3.6.5 (default, May  3 2018, 10:08:28) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import imp

En 3.7, plein de modules importent imp, mais les DeprecationWarning ne sont pas montrés, car ça arrive dans des codes importés. En revanche, si dans le module principal, vous importez imp:

$ python3.7 
Python 3.7.0+ (default, Jun 28 2018, 14:08:14) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import imp
__main__:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

Ça donne une info importante, sans foutre un mur de warnings à chaque lancement.

Une autre aide est l’apparition, toujours en 3.7, du mode développement de Python avec -X dev qui active tout un tas de comportements aidant au développement:

Évidemment, tout ça ne sert pas à grand-chose si on ne sait pas ce qu’il faut en faire. Et ça demande du temps et du travail, ce que l’amateurisme ne permet pas forcément.

Enfin je dis ça. La plupart des employeurs s’attendent à tout, tout de suite également. Donc au final, n’est-ce pas la culture générale de notre industrie qui est en train de virer dangereusement vers le vite fait mal fait ?

Même si il y a clairement une question de compétence (un prof de maths est généralement compétent en maths, alors que j’attends toujours de rencontrer un prof d’info qui est capable de mettre quelque chose en prod), la pression du marché a créé des attentes impossibles…

L’informatique n’existe comme secteur économique que depuis quelques décennies, contre des siècles pour la plupart des autres disciplines scientifiques. Pourtant on exige d’elle le même niveau de productivité. Il a bien fallut rogner quelque part, et c’est la fiabilité qu’on a choisit.

Quand il y 20 ans, on rigolait en comparant le debuggage de Windows a la réparation d’une voiture, et la punchline sur le redémarrage, ce n’était pas grave: un peu de virtuel dans un monde plein d’encyclopédies papier, de cabines ou bottins téléphoniques et autres cartes routières.

Aujourd’hui que notre monde entier dépend du fonctionnement de nos conneries codées à l’arrache, c’est plus emmerdant. Et ça explique aussi pourquoi le téléphone de ma grand mère fonctionne toujours mieux pour faire des appels que mon putain de smartphone a 600 euros. Mais je peux draguer une meuf par texto en faisant caca à l’aéroport. Tout a un prix.

Guido van Rossum s’offre “des vacances permanentes” 17

jeudi 12 juillet 2018 à 22:56

Le BDFL (Dictateur Bénévole Bienveillant À Vie) et créateur du langage Python vient d’annoncer qu’il laissait son bébé à la communauté.

Sans quitter son rôle de core dev, et en suggérant qu’il passe plus de temps en tant que mentor de ses collègues sur le projet, il abandonne néanmoins son pouvoir de décision absolu sur le langage et son rôle de validateur ultime de chaque introduction de nouvelles fonctionnalités.

Dans son email, il ne passe pas le pouvoir à qui que ce soit ni n’invite à un mode de direction particulier. Il suggère que l’équipe actuelle des core devs, les développeurs ayant les droits de commit sur le repository du langage, choisisse le format qui leur convienne le mieux pour diriger à présent le projet.

Un moment important dans la vie de Python, mais aussi un événement qui demande beaucoup de remise en contexte.

Python-idea, Python-dev et les PEP

Durant les dernières années, les décisions de modification du langage passaient généralement par plusieurs phases.

D’abord, la proposition faite sur la mailing list python-idea. Cette liste est publique. Tout le monde peut y participer et y donner son avis.

Après débat, si le climat n’est pas hostile (ce qui est une notion TRES subjective), un des core devs suggère généralement à l’auteur de l’idée d’écrire un PEP.

Le PEP (Proposition d’Amélioration de Python) est l’équivalent d’une RFC pour Python. C’est un document formalisant une proposition d’amélioration du langage et de son écosystème. Le plus connu est bien entendu le PEP 8.

S’ensuit encore plus de débats, et des modifications itératives du PEP, jusqu’à, soit l’abandon du projet par l’auteur, soit l’oubli, soit le verdict de Guido.

Ce dernier est définitif: le PEP est approuvé ou rejeté. Dans le premier cas, il sera appliqué. Dans le second, il part à la poubelle.

Quelque part à la fin de ce processus, les implémenteurs du PEP discutent en plus petit comité, sur la liste python-dev, de la réalisation.

Guido a toujours, jusqu’ici, été donc la dernière étape de filtre de cet enchaînement. A la foi gardien de sa philosophie du langage, et goulot d’étranglement.

Il y a eu quelques améliorations, comme la nomination ponctuelle de BDFL délégués pour certains sujets, mais le format reste quand même globalement le même.

Pride and prejudice

Ce système a l’avantage d’être très ouvert. Tout le monde peut participer. Peu de personnes le font pourtant, par rapport à la masse d’utilisateurs de Python, mais c’est déjà beaucoup si on considère l’inertie que ça implique.

Le mail est un format très accessible, décentralisé, ouvert et standard. Son austérité évite l’effet réseau social qu’on voit trop souvent sur les outils plus modernes.

En contrepartie, le processus est très flou. Il n’y a pas de vote a proprement parler. Les threads s’enchaînent un peu n’importe comment. Les nouveaux venus ne sachant pas comment utiliser l’outil le font imparfaitement. Et le media ne permet pas facilement de suivre la continuité à court terme et à long terme de grand-chose, ni en termes de flux, ni en termes de référence, ni en termes de recherche.

Mais c’est aussi l’aspect social qui est problématique. Il n’y a aucune organisation claire dans la liste, et tout le monde semble “au même niveau”. Or ce n’est pas absolument pas le cas. La réputation et les participations passées, ainsi que les caractères des participants et leurs relations jouent énormément sur le processus. Le timing également.

Ça parait équitable sur le papier, mais c’est une illusion, et en prime cela implique des frictions qui pourraient être évitées. Par exemple quand un étudiant demande à Larry Hastings de l’aider à faire ses devoirs ou qu’un auteur de PEP cite le Zen of Python à Tim Peters. Inversement, quand vous proposez une idée, et que tout le monde vous envoie péter, mais que 6 mois plus tard, Brett Cannon dit que c’est intéressant et que tout le monde crie au génie, ça fout les boules.

Ajouté à cela, la qualité du débat qui peut varier du tout au tout : du caprice à la revue technique, en passant par le mail à côté de la plaque, la crise d’égo, l’appel au calme, le récap qui aide tout le monde, les idées brillantes, les suggestions étonnantes, les redites, et le spam.

Et évidement, la réponse par défaut est toujours non.

Se faire entendre là dedans est très difficile. Garder son calme l’est souvent aussi. Mais surtout, persévérer à travers le processus durant le temps nécessaire pour l’aboutissement de celui-ci est une épreuve épuisante, irritante, et ingrate. Surtout quand 9 fois sur 10, elle se finit par un rejet.

Un rejet cependant, n’est pas la mise à mort d’une idée. L’année suivante on peut relancer le débat, et ainsi de suite, comme un politicien à l’assemblée qui veut faire passer son projet de loi. Et comme au Parlement, la même idée présentée par une nouvelle personne, ou avec un autre timing peut avoir un résultat radicalement différent.

Un anus pour les rassembler tôt

GvR, bien qu’au sommet de cette pyramide, n’est pas isolé de tout cela, bien au contraire. Il est une des rares personnes qui doit trancher sur presque tout, et donc se coltiner une bonne partie des débats. Et des réactions quand il rejette l’idée d’autrui.

À ceci se rajoute sa présence sur les bugs trackers, et bien entendu son activité de développeur.

Un rôle difficile, fatigant, et un vrai sacerdoce pour quelqu’un qui pourrait dire “allez tous vous faire foutre, j’ai pondu ce langage utilisé par des millions de personnes tout seul, je sais mieux que vous” au lieu de prendre en considération l’avis du public.

Un rôle, je vous le rappelle, qu’il tient depuis plus de 20 ans.

Depuis la 3.4, on sent le poids commencer à peser sur ses épaules. A 62 ans, et toujours avec un travail à plein temps chez Dropbox (qui heureusement lui laisse le loisir de travailler sur Python), on peut imaginer l’énergie nécessaire pour s’impliquer encore autant.

Cela se ressent particulièrement dans ses mails et ses tickets. Une sorte de lassitude. Et une retenue qu’on devine de plus en plus difficile.

Honnêtement, je ne sais pas comment il a fait pour ne pas exploser sur quelqu’un. Dans certains échanges que j’ai eus avec lui sur la liste ou sur github, si nos rôles avaient été inversés, j’aurais sans aucun doute été bien moins diplomate.

sys.exit() maintenant, avant que la charge ne devienne trop pesante, est à mon sens un acte de grande classe.

Comme un comédien, un musicien, un metteur en scène ou un écrivain qui fait ses adieux au sommet de son art. J’aurais aimé que Lucas, Spielberg et Besson aient su en faire autant.

Mais pour bien comprendre à quel point cette décision de laisser sa place demande du courage, il faut réaliser une chose: il laisse son enfant dans les mains des autres. L’œuvre de sa vie. Une réalisation colossale qui plus est, qui a généré des centaines de milliers d’emplois, fait tourner des milliers de projets.

Moi, j’ai du mal à m’imaginer arrêter d’écrire ce misérable petit blog. Et pourtant j’y ai pensé souvent.

L’étincelle qui a fait déborder la moutarde, c’est bien entendu le débat sans fin sur l’expression d’assignation, et la critique vigoureuse de son acceptation. (C’est vrai que c’était chaud)

Guido en a eu ras le cul, quoi.

So what now ?

Personnellement, j’accueille la nouvelle avec un mélange d’espoir et de mélancolie.

Python n’aurait jamais été ce qu’il est aujourd’hui sans la constante vigilance de son BDFL.

Dire “non” est la partie la plus importante pour sculpter une expérience. C’est ce qui fait la différence entre Gates et Jobs, Van dam et Lee, Hitler et… heu… non.

C’est ce qui fait qu’on n’a des lambda bridées et pas d’opérateur pour ajouter un élément dans une liste. Qu’on le veuille ou pas.

Et c’est ce qui fait qu’un langage né en fucking 1985 (avant Java !) tient toujours la route aujourd’hui. A gardé sa lisibilité. Sa facilité de prise en main. Et continue d’être utile.

C’est un travail de titan !

Néanmoins je ne peux m’empêcher de noter que les dernières fonctionnalités majeures de Python ont été introduites de manière brouillonne. Je pense notamment au type hints et à asyncio, que Guido a bdfl -f sans donner le temps de mûrir leur design. Elles ont mis plusieurs versions à ne serait-ce qu’être utilisables, et méritent encore du travail.

Je pense aussi que Python est prêt pour des ajustements, et espère que passer le flambeau à l’équipe de core dev va apporter du renouveau au langage.

En effet, il est peu probable qu’un nouveau BDFL s’installe, et bien qu’il soit trop tôt pour faire une prédiction, la mise en place d’un quorum semble plus probable. Dans ce cas, il faudra sans doute un vecteur de vote. Et de là, qui sait, naîtra peut-être un complément ou replacement à python-idea.

Je l’espère en tout cas.

Sortir de l’usage de la mailing list pour un outil plus encadré, mettant les propositions en avant, avec un système de sondage, des procédures plus claires, des références plus faciles au passé, et une distinction explicite du statut des personnes qui s’expriment.

Bref, faciliter l’accès au processus, tout en permettant une gestion plus saine. On ne voudrait pas que Victor ou Yuri nous posent leur dem dans les 6 mois pour cause de pétage-de-cablite aigue.

Également, qui sait, peut-être reviendra-t-on – pour le meilleur ou pour le pire ? – sur des BanDFL tel que:

Difficile de croire que le débat ne sera pas relancé maintenant que Cerbère ne garde plus les Champs Élysées.

Évidemment, il est logique qu’il y ait un temps de flottement. D’abord par respect pour le père de Python. Ça ferait un peu GoT d’enchaîner quand même. Ensuite parce qu’il va falloir qu’un nouveau mode de fonctionnement émerge, et prouve son efficacité.

Quoi qu’il arrive

Merci à Guido Van Rossum pour ce cadeau immense.

On va essayer de ne pas trop l’abîmer.

L’expression d’assignation vient d’être acceptée 18

mardi 3 juillet 2018 à 11:15

Après des mois de débats sur python-idea (mailing list sur laquelle, je vous le rappelle, tout le monde a le droit de participer), Guido a validé la PEP 572. (foo := bar) sera donc un code valide en Python 3.8.

Je n’avais pas vu de feature plus controversée depuis les f-strings. Et je gage que, comme les f-strings, après un temps d’adaptation, la communauté va se demander comment on a vécu sans auparavant.

Un peu d’histoire

Il existe deux grandes catégories d’instructions dans les langages de programmation. Les déclarations et les expressions.

La déclaration, en anglais “statement”, est une action indépendante formulée sur une ligne. C’est une unité syntaxique, et une déclaration ne peut être contenue dans une autre déclaration sur la même ligne.

Ex:

import os

est une déclaration. Un import est tout seul sur sa ligne et ne se combine pas avec d’autres instructions.

L’expression, elle, est une combinaison de littéraux, variables, d’opérateurs et d’appels de fonctions qui retournent un résultat. Les expressions peuvent contenir d’autres expressions, et elles peuvent être contenues dans une déclaration.

Ex:

1 + 1 

est une expression. On peut, en effet, assigner le résultat de ce code à une variable, le passer à une fonction en paramètre ou le tester dans une condition.

Les langages, comme le COBOL, qui privilégient le style impératif utilisent majoritairement des déclarations. Les langages, comme LISP, qui privilégient le style fonctionnel, utilisent majoritairement des expressions.

Très souvent néanmoins, les langages populaires font largement usage des deux. L’expression est plus flexible et plus puissante. La déclaration force une opinion sur la structure du programme et évite les divergences de style. Selon la philosophie que l’on souhaite donner à son bébé, un créateur de langage va donc s’orienter plus vers l’une ou l’autre.

Python est multiparadigme et soutient le style impératif, fonctionnel et orienté objet. Il possède donc non seulement des expressions et des déclarations, mais également souvent les deux versions pour une même instruction.

Ex, les déclarations:

squares = []
for x in numbers:
    squares.append(x * x)

def mean(x):
    return x * x / 2

if is_ok:
    print(welcome)
else:
    print(error)

ont des expressions équivalentes:

[x * x for x in number]

lambda x: x * x / 2

print(welcome if is_ok else error)

Parfois Python choisit d’orienter le style du programmeur, non pas évitant de fournir des expressions, mais par le biais de la grammaire imposée. Ainsi il oblige à indenter, et n’autorise pas les lambdas à contenir de déclaration. C’est une autre stratégie pour imposer une philosophie au langage. Pour Python, la philosophie est que la capacité à s’exprimer doit rester riche, mais pas au détriment de la capacité à comprendre le code.

Une particularité de Python, c’est que l’assignation, c’est-à-dire le fait d’associer une valeur à une variable, est une déclaration. Ex:

a = 1

Comme les déclarations ne peuvent contenir d’autres déclarations, cette syntaxe interdit:

if a = 1:  

Ce n’est pas le cas dans de nombreux langages populaires, comme le C, PHP, le JS… Exemple en Ruby:

if (value = Settings.get('test_setting'))
  perform_action(value)
end

Ici, non seulement on assigne le résultat du get() à la variable value, mais en plus, on teste le résultat du get() avec le if.

Actuellement ceci est impossible en Python, et le même code serait:

value = Settings.get('test_setting')
if value:
  perform_action(value)

Ce n’est pas une erreur, c’est un choix de design. En effet une source de bug très courante en programmation est de vouloir faire une comparaison, mais de taper une assignation.

Ainsi, quelqu’un voudrait faire:

while reponse == "oui":
    ...
    reponse = input('Voulez-vous continuer ?')

Mais ferait:

while reponse = "oui": # erreur subtile et difficile à voir
    ...
    reponse = input('Voulez-vous continuer ?')

Ce qui ne compare pas DU TOUT la variable. Et en plus change son contenu. Dans ce cas précis, le résultat est une boucle infinie, mais parfaitement valide et qui ne provoquera pas d’erreur.

Pour éviter ce genre de bug que même les programmeurs aguerris font un jour de fatigue, la syntaxe a tout simplement été interdite en stipulant que l’assignation était toujours une déclaration.

La PEP 572

L’absence de cette fonctionnalité a eu d’excellents bénéfices. Je le vois régulièrement dans mes salles de classe, ce bug. La SyntaxError qui résulte de cette faute en Python permet de l’attraper avant qu’il ne fasse le moindre dégât.

Car c’est tout le problème de cette erreur: si la syntaxe est valide, le bug est silencieux, le code tourne, il ne fait juste pas du tout ce qu’on lui demande. C’est la plus pernicieuse des situations, avec des conséquences qui peuvent ne se déclarer que bien plus loin dans le code et une séance de débuggage des plus irritantes.

A mon sens, c’était une excellente décision de design.

Pourtant la PEP 572 revient dessus, en proposant, comme pour lambda, la boucle for ou la condition, un équivalent sous forme d’expression.

Pourquoi ?

Et bien Python doit aussi satisfaire les programmeurs chevronnés, qui en ont marre de se retrouver avec des situations comme :

match1 = pattern1.match(data)
if match1:
    print(match1.group(1))
else:
    match2 = pattern2.match(data)
    if match2:
        print(match2.group(2))

Certes, ce code est clair et facile à comprendre, mais il est très verbeux. On doit faire avec une indentation artificiellement induite par la limite de la syntaxe, et non par la logique du raisonnement qui est ici parfaitement linéaire.

Comment donc réconcilier le désir d’éviter le fameux bug tout en satisfaisant les besoins d’expressivité, légitimes une fois qu’on quitte le nid des débutants ?

La solution proposée est triple :

Le nouvel opérateur choisi est := et il ne peut exister qu’entre parenthèses. Il peut être utilisé là où un simple = ne serait pas autorisé. Mais, il ne peut PAS être utilisé là où on peut utiliser =. Le but est de ne jamais mettre ces deux opérateurs en concurrence: une situation permet l’un ou l’autre, jamais les deux.

= ne change pas. La seule différence, c’est qu’à partir de Python 3.8, vous aurez le droit de faire:

if (match1 := pattern1.match(data)):
    print(match1.group(1))
elif (match2 := pattern2.match(data)):
    print(match2.group(2))

L’opérateur := permet donc bien, dans des situations très précises, d’obtenir un code plus cours et élégant, sans introduire pourtant d’ambiguïté et donc de bug potentiel. Il autorise une nouvelle forme d’expressivité, mais sa syntaxe est très marquée: impossible de le confondre avec son frère, et les parenthèses l’isolent du reste du code.

On ne se pose pas non plus la question de quand choisir = et := puisque:

a = 1

est valide, mais pas:

a := 1

Bien que:

(a := 1)

soit valide, personne n’aura envie d’utiliser vainement cette forme plus lourde.

L’usage de := est donc marginal, et cantonné à des cas particuliers.

Une opinion, c’est comme un trou du cul…

Personnellement j’étais mitigé sur l’idée. De plus, j’aurais préféré l’usage du mot clé as, puisqu’on l’utilisait déjà dans les imports, les context managers et la gestion des exceptions.

Chaque ajout d’expression rajoute également la possibilité d’abus. Si vous avez déjà vu ces horreurs à base de lambdas imbriquées ou d’intensions sans fin, vous savez de quoi je parle.

Avec :=, on peut vraiment se lancer dans du grand n’importe quoi:

Pour reprendre l’exemple de la PEP:

while True:
    old = total
    total += term
    if old == total:
        return total
    term *= mx2 / (i*(i+1))
    i += 2

est très clair.

En revanche:

while total != (total := total + term):
    term *= mx2 / (i*(i+1))
    i += 2
return total

Est une abomination qu’il faut purger par le feu.

Mais par expérience, j’ai rarement vu en 15 ans de Python beaucoup d’abus de ses fonctionnalités avancées. Les bénéfices ont toujours dépassé le coût d’une large marge. Pourtant entre les décorateurs, les dunder methods et les meta classes, il y a matière à messe noire.

Par ailleurs j’avoue que je suis ravi de pouvoir enfin faire:

while (data := io.get(x)):

Et:

[bar(x) for z in stuff if (x := foo(z))]

La PEP mentionne aussi un exemple que je n’avais pas prévu, et qui souffle le chaud et le froid:

diff = x - x_base
if diff:
    g = gcd(diff, n)
    if g > 1:
        return g

Peut devenir:

if (diff := x - x_base) and (g := gcd(diff, n)) > 1:
    return g

Comme l’auteur, j’approuve le fait que la première version est inutilement verbeuse, mais contrairement à lui, je trouve que la seconde est bien trop complexe pour être scannée d’un coup d’œil.

En revanche:

diff = x - x_base
if diff and (g := gcd(diff, n)) > 1:
    return g

est tout à fait à mon goût.

Ceci démontre bien qu’il va falloir un temps d’adaptation avant que la communauté trouve l’équilibre entre Perl et BASIC. Quoi qu’il en soit, on n’aura pas à s’en soucier avant l’année prochaine, et même d’ici là, peu de code pourra en faire usage avant que la 3.8 soit largement installée.

De mon côté je m’attends à ce qu’on ignore majoritairement cette fonctionnalité, jusqu’au moment où elle apparaîtra dans un coin de l’esprit le temps d’un besoin ponctuel et local, pour être oubliée à nouveau jusqu’à l’occasion suivante. Comme Dieu Guido l’aura voulu. D’ailleurs, côté enseignement, je ne compte pas introduire l’opérateur dans mes cours, ou alors dans une section bonus, à moins qu’un participant ne pose la question.

Aller, vous pouvez râler en commentaire maintenant :)

Le don du mois: mobx 7

lundi 2 juillet 2018 à 12:41

Si vous avez suivi un peu les différents dons du mois au fur et à mesure de la vie du blog, vous avez du vous rendre compte de 2 choses:

En fait le seul et unique projet JS supporté a été VueJS. C’est dire que la barre est haute, étant donné la qualité exceptionnelle de ce projet.

Donc quand je vous dis que j’ai donné 50 balles à Mobx, c’est que le projet déchire. Et il déchire malgré le fait qu’il soit codé en JS.

Mobx permet, en gros, de surveiller les modifications à une structure de données. Vous posez un marqueur sur la structure, et un sur les fonctions utilisant la structure, et c’est tout:

class TodoList {
    @observable todos = []; // dire à mobx de surveiller
}

...

@observer // dire à mobx de tenir à jour
class TodoListView extends Component {
    render() {
        return 
    {this.props.todoList.todos.map(todo =>)}
}

C’est là la brillance du système: malgré sa simplicité, mobx va récursivement réagir à toute modifications, même sur des données complexes imbriquées; Et calculer toutes les dépendances de chaque fonction pour ne les appeler qu’au meilleur moment.

C’est facile à utiliser, et étonnamment rapide à exécuter.

Le résultat ? Quand un client me force à utiliser ReactJS, je saute redux, et je met Mobx à la place. Ca donne presque l’impression d’utiliser Vue: le code de manipulation d’état est simple à comprendre, gentil sur le CPU et les mutations d’état restent courtes et élégantes. Le reste est toujours moche, mais ça on y peut rien.

Bref, mobx est ce qui rend react acceptable. Et tout ce qui peut apaiser la douleur du dev en front-end n’a pas de prix.

J’ai regardé le code source, et la popote interne est bien complexe. Mais à chaque fois que je veux l’utiliser je me dis que ça ne pourra pas être si simple… et si.

La page de don, c’est par là.

Python 3.7 sort de sa coquille 20

jeudi 28 juin 2018 à 18:54

La 3.4 était la première version 3 à valoir le coup, et a donc été le déclencheur de la migration 2->3 qui trainait depuis si longtemps.

La 3.5(.3) a rendu asyncio utilisable, incluant async / await et corrigeant le bug abusé de get_event_loop().

La 3.6 est mon chouchou. Sa meilleure intégration de Pathlib et les f-strings en font un plaisir total à utiliser. En plus black ne tourne que dessus. Je suis autant que possible en 3.6, je l’ai même installée sur une vieille centos 7 chez un client.

Alors que vaut cette 3.7, et est-ce qu’il faut migrer ?

Et bien avec des améliorations de perfs partout et une syntaxe simplifiée pour les classes, c’est une belle release. La 3.6 va être bien plus facile à avoir sous linux pendant un bon bout de temps et suffit amplement, donc je ne vais pas forcer le pas. Mais bon je l’ai quand même compilée par acquis de conscience.

Regardons ce qu’il y a au menu.

Les data classes

Clairement la feature phare de la release, les data classes sont une manière plus concise d’écrire des classes, s’inspirant de la bibliothèque attrs dont elles n’implémentent qu’un sous-ensemble.

Une très bonne nouvelle, car je n’installais jamais attrs: dépendre d’une lib juste pour ça, m’embêtait et pour la sérialisation/validation j’utilise marshmallow.

Par exemple:

from dataclasses import dataclass

@dataclass
class Achat:
    produit: str
    prix: float
    quantite: int = 0

Ce qui va générer une classe toute ce qui a de plus normale, mais dont le __init__, __repr__ et __eq__ sont automatiquement créés. Vous pouvez bien entendu ajouter les méthodes que vous voulez, comme d’habitude.

Il ne reste plus qu’à faire:

>>> print(Achat("foo", 2))
Achat(produit='foo', prix=2, quantite=0)

Toute la magie est sélectivement désactivable, et une méthode __post_init__ est ajoutée à la classe qui fait exactement ce que vous pensez que ça fait.

En prime, on a aussi dataclasses.field qui permet de fournir une factory pour un paramètre (typiquement list, tuple, dict…).

Puis, comme un bonheur ne vient jamais seul:

>>> from dataclasses import asdict, astuple
>>> print(asdict(Achat("foo", 2)))
{'produit': 'foo', 'prix': 2, 'quantite': 0}
>>> print(astuple(Achat("foo", 2)))
('foo', 2, 0)

C’est récursif sur les dicts, lists, tuples et dataclasses \o/

Enfin, pour finir:

>>> from dataclasses import replace
>>> a = Achat("foo", 2)
>>> b = replace(a, quantite=3, prix=1)
>>> print(a, id(a))
Achat(produit='foo', prix=2, quantite=0) 140275795603296
>>> print(b, id(b))
Achat(produit='foo', prix=1, quantite=3) 140275775561456

Ouai ça déchire.

P.S: y un backport pour la 3.6

asyncio++

Much love to asyncio dans cette version.

Déjà, un truc qui aurait dû être là dès le début, la nouvelle fonction asyncio.run(), qui masque le setup de l’event loop pour vous.

On passe de :

loop = asyncio.get_event_loop() 
loop.run_until_complete(coro)

à:

asyncio.run(coro)

Juste ça, ça fait vachement moins peur aux gens. Et en prime ça évite qu’ils commencent à chercher la merde avec un setup custom de loop.

asyncio.current_task() retourne la tâche dans laquelle on est. D’ailleurs, un détail, mais on a maintenant l’équivalent de thread local, mais pour la coroutine en cours.

asyncio.get_running_loop() retourne la boucle courante, mais seulement si elle existe. Elle lève une exception plutôt que de créer une loop comme get_event_loop() si aucune loop n’est présente.

StreamWriter.wait_closed() permet d’attendre qu’un stream se ferme. Les gars de aiohttp doivent être contents.

Task.get_loop() retourne la boucle de la tâche. Pour le multi-threading avec plusieurs loops, c’est cool.

loop.create_server() a maintenant un argument start_serving qui contrôle si on veut le lancer immédiatement. J’ai toujours du mal à croire que des dev qui sont capables de participer à la stdlib ont pu commiter un code qui instancie et enchaine sur un effet de bord. Heureusement c’est corrigé.

Les handlers retournés par loop.call_later() retournent leur ETA avec .when() et ont une méthode .cancelled().

Les intensions acceptent maintenant async/await.

Et enfin, les exceptions des tâches annulées ne sont plus loggées. Parce que forcément quand on crashait tout, le log devenait un peu chargé…

Bon, asyncio était déjà très utilisable en 3.6, n’exagérons pas. L’important étant d’utiliser le mode debug, gather() et run_until_complete(), ce qui devrait être écrit en gros, en rouge dans la doc.

Mais toutes ces modifications sont bienvenues.

Ah, oui, les perfs ont été aussi améliorées… Mais c’est le cas partout.

Des perfs

Le focus sur les perfs de Python augmente doucement. La 3.6 avait amorcé la tendance, et ça se confirme. J’attends d’avoir des retours sur des mises en prod un peu serieuses pour savoir si ça a payé.

Le temps de démarrage de Python est d’ailleurs pas mal pointé du doigt. Certes, on est pas au niveau de loutre sclérosée que constitue nodejs au réveil, mais c’est pas une référence. Donc, des choses sont mises en place. Notamment python -X importtime qui va afficher le temps que prend chaque import.

Des aménagements ont aussi été faits pour accélérer le module typing, maintenant que l’usage des type hints pour les annotations est entériné. Un side effect sympa est que les classes que vous allez écrire seront plus rapides à instancier, et les méthodes plus rapides à résoudre.

D’ailleurs, les type hints sont maintenant résolus paresseusement, à la fois pour améliorer la vitesse de chargement et pour faciliter l’auto-référencement.

breakpoint()

breakpoint() est techniquement un alias configurable à import pdb; pdb.set_trace(). Ça a l’air de rien, mais c’est super:

Ça vient, bien entendu avec une variable d’environnement et d’un hook dans sys pour custo le comportement.

Quelques détails

Dicts ordonnés

La spec garantit que les clés vont garder leur ordre d’insertion. C’était déjà le cas en 3.6, la 3.7 rend juste la mesure officielle.

Ne jetez pas OrderedDict à la poubelle pour autant, car il préserve l’ordre des clés après suppression également.

async/await sont des mots clés

Et ne peuvent donc plus être écrasés par erreur. C’est juste l’application de l’annonce faite à l’introduction de ces mots clés.

Des sœurs pour __getitem__ et __getattr__

On va pouvoir définir un __getattr__ sur les modules (surtout utile pour le lazy loading) et un __class_getitem__ pour pouvoir faire MaClass[] sans utiliser de metaclass.

Traduction de la doc

Le processus pour avoir des docs dans d’autres langues est maintenant officiel. Pour l’instant le jap, le koréen et le kokorico

DeprecationWarning plus visibles

La connerie de les cacher a été corrigée. Qui a pensé que c’était une bonne idée ? Mais seulement pour le script principal, ce qui va permettre aux dev des libs de les voir sans faire chier les utilisateurs.

Debug mode

python -X dev va devenir votre nouvel ami, activant tout un tas de fonctions de debug coûteuses en production. Notamment plus de warning, asyncio debug mode, le faulthandler qui dump la stacktrace en cas de catastrophe, etc.

Des pyc reproductibles

Un même fichier donnera maintenant toujours un même .pyc. C’est pour les packagers et les amateurs de sécu.

Meilleur ImportError

L’exception va maintenant afficher le nom du module et son __file__ path si from ... import broute. Ça va rendre les imports circulaires, la plaie des gros projets Python, plus faciles à debugger.

packaging

Introduction de importlib.resources, un remplacement pour le détestable pkg_resource qui va rendre sans regret mon article obsolète.

Autre ajout notable: README.rst est maintenant reconnu et ajouté automatiquement quand on fait son paquet cadeau. Puisque maintenant pypi accepte le markdown, ça aurait été cool de le faire avec les .md également.

time est plus précis

Sensible à la nanoseconde. Perso je m’en bats les steaks mais je me suis dit que je ferai passer l’info.

unittest -k

Copieurs.

namedtuple supporte les valeurs par defaut

Rien à ajouter, si ce n’est qu’entre SimpleNamespace et les dataclasses, je crois qu’on a de quoi voir venir. Même si j’aimerais avoir un literal pour les namedtuples sous la forme de (foo=1, bar=2) mais ça a été refusé.

Ajouts à Contexlib

Quelques outils en plus, dont un context manager qui ne fait rien (rigolez pas, c’est super utile !), et des contexts managers async.

lifting pour subprocess

Ok, plutôt bottox. C’est cosmétique, mais c’est bienvenu: des aménagements pour rendre les appels un poil plus courts, notamment dans le cas de la capture des stdx.



Et encore plein d’autres mini trucs.

C’est dispo en DL pour windows et mac. Pour linux, comme d’hab, soit on attend la mise à jour des depôts/ppa/etc, soit on compile à la main (étonnamment facile, si on se rappelle de faire make altinstall et pas make install), soit on utilise l’excellent pyenv et pyenv install 3.7.