Python 3.7 sort de sa coquille


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

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.

20 thoughts on “Python 3.7 sort de sa coquille

  • asinca1000

    Et est-ce qu’on a un async contextmanager dans la lib standard ? Jusque là j’utilisais asyncio-extras pour ça…

  • lucas

    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.

  • ashgan

    y manque pas un bout de truc a propos des dataclass?

    “mais dont le init, repr et eq automatiquement”

    genre “sont crées”??

  • Sam Post author

    @lucas : si on trouve une meilleure implémentation, on la mettera dans collections.UnorderedDict avec mention dans la doc de dict.

  • Lekva

    @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à.

  • Victor Stinner

    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.

  • Sam Post author

    Ouai mais c’est pas encore officiel. Peut etre pour 3.8

  • Victor Stinner

    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).

  • aa

    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

  • Julien

    “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.

  • Digamma

    Dans les points positifs de breakpoint(), c’est builtins et pas builins ^^

  • Stéphane

    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 ?

  • Sam Post author

    Merci à tous pour les corrections.

    @ulien: oui mais ça ne fait pas partie de la spec.

  • mdupuy

    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 ;)

  • entwanne

    Même si les dict sont maintenant ordonnés, il reste un avantage aux OrderedDict : 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 les dict.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.