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:
- C’est dans les builtins, donc plus facile à découvrir. Les débutants utilisent très peu pdb alors que c’est un outil formidable.
- Plein de gens oublient cette incantation mystique ou font des fautes de frappe.
- C’est facile à spotter dans le code, on peut l’highlither différement.
- C’est uniforme. Si on veut utiliser un autre débuggeur, on le plug. Mais on utilise toujours la même fonction. On peut même désactiver cette fonction pour éviter de balancer un break point en prod.
- C’est clair. Les débutants n’ont aucun mal avec
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.
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
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
.
Et est-ce qu’on a un async contextmanager dans la lib standard ? Jusque là j’utilisais asyncio-extras pour ça…
Erf… J’ai lu trop vite. C’est donc oui.
Pour moi, la killing feature de la 3.7, c’est le report de l’évaluation des annotations (annotations postponing, dans la doc). C’est juste une libération.
Le reste est fun, et je note que même en parcourant la page officielle des nouveautés de la 3.7, j’avais pas vu (et oublié) les data classes.
Sinon, l’inscription «officielle» que les dictionnaires doivent être ordonnés, c’est une erreur pour moi, quoiqu’en dise Guido. C’est pas utile dans le cas général, et ce n’est possible que parce qu’une optimisation sur l’implémentation à été trouvée, et que son side-effect est de conserver l’ordre.
Donc, si une encore meilleure implémentation des dict est trouvée, mais que celle-ci ne conserve pas l’ordre, il se passe quoi ? On refuse l’opti ? Ou on punit tous ceux qui font confiance à la doc ?
Pour moi, le fait que l’ordre soit conservé est une propriété d’implémentation pour ce genre de structure de donnée. OrderedDict était justement là pour combler le manque.
Gros point noir pour moi, mais sinon, je suis content.
y manque pas un bout de truc a propos des dataclass?
“mais dont le init, repr et eq automatiquement”
genre “sont crées”??
Bien vu
@lucas : si on trouve une meilleure implémentation, on la mettera dans collections.UnorderedDict avec mention dans la doc de dict.
@lucas
Je pense que la motivation principale pour garder l’ordre dans les dict c’était surtout pour avoir le même ordre entre l’appel d’une fonction/méthode et le **kwargs reçu.
Même si la plupart du temps on peut pointer un soucis de design/d’API, je me suis assez souvent fait cette réflexion là.
Le dict qui conserve l’ordre est un effet de bord de l’implémentation “compact dict” pour Python 3.6 de INADA Naoki inspiré de PyPy. Cette implémentation est conçue pour baisser l’utilisation mémoire (20 à 30%). Oh tiens ça conserve l’ordre, sympa ;-) Sinon faut pas le dire (détail d’implémentation) mais dict conserve aussi l’ordre après suppression.
Ouai mais c’est pas encore officiel. Peut etre pour 3.8
Je n’ai pas ajouté TCP_NODELAY, mais c’est Yury Selivanov évidemment !
https://hg.python.org/cpython/rev/3f7e4ae9eba3
Yury a ajoute les mots clé async et await, conçu les générateurs et list comprehensions asynchrones, écrit uvloop et aiopg, etc. C’est juste un génie et la bonne étoile d’asyncio.
Oh sinon, TCP_NODELAY est déjà dans 3.6. Niveau perf, Python 3.7 ajoute sendfile() à asyncio pour les transferts sans recopie mémoire (copie faite dans le noyau).
Merci victor pour ces corrections !
Petite typo, s/init_t_/__init/ ?
je l’ai même installéE sur une vieille centos
OuaiS ça déchire.
qui controle -> qui contrôle
serieuses -> sérieuses
de l’outre sclérosée -> la loutre sclérosée ?
Des aménagements ont aussi été faitS
seront plus rapideS
méthodes plus rapideS
Ça à l’air de rien -> Ça a l’air de rien
C’est dans les builTins
highlitHer différeMMEnt
soeurs -> sœurs
imports circulaires, la plaie des gros projets Python, plus facileS à debugger
pypi acceptE le markdown
soit on utilise l’excellent on install pyenv -> soit on utilise l’excellent pyenv et
“Ne jetez pas OrderedDict à la poubelle pour autant, car il préserve l’ordre des clés après suppression également.”, il me semble que dict aussi, conserve l’ordre des clefs à la suppression.
Dans les points positifs de breakpoint(), c’est builtins et pas builins ^^
acquit de conscience -> acquis …
namestuples -> namedtuples
soit on utilise l’excellent on install pyenv -> ? j’ai l’impression que deux formulations se sont mélangées ?
Merci à tous pour les corrections.
@ulien: oui mais ça ne fait pas partie de la spec.
Yo !
Ça serait sympa de donner le lien bugs.python.org du “bug abusé de get_event_loop”, parce que là avec juste un lien vers une issue github vaguement liée, impossible de le retrouver. Merci ;)
PR: https://github.com/python/asyncio/pull/452
En gros, get_event_loop ne retournait pas toujours l’évent loop en cours avant.
Même si les
dict
sont maintenant ordonnés, il reste un avantage auxOrderedDict
: l’ordre des éléments y est significatif lors de la comparaison.Deux
OrderedDict
sont en effet considérés comme différents s’ils contiennent les mêmes éléments ordonnés différemment. Ce qui n’arrivera jamais sur lesdict
.