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.
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
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.
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() est techniquement un alias configurable à import pdb; pdb.set_trace()
. Ça a l’air de rien, mais c’est super:
debugger
en JS parce que c’est simple de comprendre ce que ça fait au premier coup d’oeil.Ça vient, bien entendu avec une variable d’environnement et d’un hook dans sys
pour custo le comportement.
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.
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.
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.
Le processus pour avoir des docs dans d’autres langues est maintenant officiel. Pour l’instant le jap, le koréen et le kokorico
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.
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.
Un même fichier donnera maintenant toujours un même .pyc. C’est pour les packagers et les amateurs de sécu.
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.
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.
Sensible à la nanoseconde. Perso je m’en bats les steaks mais je me suis dit que je ferai passer l’info.
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é.
Quelques outils en plus, dont un context manager qui ne fait rien (rigolez pas, c’est super utile !), et des contexts managers async.
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
.