La communauté Python est assez d’accord ces derniers temps. Maintenant que le plus gros de la débâcle Python2/3 est derrière nous (en attendant le contre coup des retardataires de 2020) et qu’on a un modèle d’IO async bien clean, les trucs qui fâchent sont assez clairement délimités:
- Le packaging.
- Le multi-core.
- Les perfs.
Sur ces questions, du travail est activement en cours.
Pour le multi-core, Eric Snow propose un modèle propre qui permettra d’utiliser plusieurs interpréteurs en parallèle en partageant des valeurs sans avoir à les sérialiser.
Concernant l’amélioration des perfs, ce sera un taf plus long, mais:
- Les opérations sur les bytes ont des perfs bien améliorées pour la 3.6.
- Victor Stinner et Yury Selivanov travaillent sur des trucs très cools. Jetez un coup d’œil aux mailling lists sur les sujets “FAT Python”, “PEP 509/510/511″ et “Explicit variable capture list” pour voir tout un tas d’optimisations prévues, ou permettant d’en prévoir.
- Pyjion, la JIT de Microsoft, un des projets les plus prometteurs pour accélérer Python, commence à faire parler d’elle.
- Dropbox, chez qui travaille Guido Van Rossum, bosse sur son implémentation de Python également.
- Et bien entendu Pypy continue son bonhomme de chemin.
Bref, y a du potentiel.
Pour le packaging, les wheels vont enfin arriver sous Linux, ce qui fait qu’on pourra bientôt pip installer des binaires sur les 3 OS majeurs. Peut-être même BSD qui sait.
Nuikta, le compilateur Python, supporte maintenant await/async. J’ai beaucoup d’attentes envers ce projet, ça mérite un don du mois :)
On est sur la bonne route.
L’année 2016 va être trop cool, et dans mon enthousiasme, je vais écrire à propos de choses que j’aimerais vraiment voir arriver dans Python.
try/except error inline
Beaucoup de codes en Python ressemblent à ça :
try:
val = faire un truc
except MonErrorALaNoix:
val = "valeur par default" |
Par exemple :
try:
val = stuff[index]
except (IndexError, TypeError):
val = None |
Ce sont des opérations si courantes qu’on a plein de raccourcis comme dict.get
ou next(i, None)
. En effet, en Python try
/except
n’est pas juste un mécanisme de gestion d’erreur, c’est un mécanisme de contrôle de flux à part entière.
Car franchement, ça fait chier de se taper 4 lignes pour écrire ça. En effet, on a bien les expressions ternaires pour les if
/else
:
val = truc if bidule else machine |
Et bien il existe un PEP (rejeté) qui propose ça:
val = faire un truc except MonErrorALaNoix: "valeur par default" |
J’adore. C’est pratique, générique, propre.
Bien entendu ça peut être abusé, comme les expressions ternaires, pour faire de la merde illisible. Mais j’ai rarement vu le cas pour les précédentes, donc ça devrait aller.
slices sur les générateurs
Les générateurs, c’est formidable. C’est iterable. On peut les utiliser partout où on utilise les listes.
Sauf si on doit les limiter en taille.
Alors là, c’est relou.
Par exemple, récupérer les carrés des nombres pairs entre 0 et 100, puis limiter le tout a 10 éléments après le 5e:
from itertools import islice
g = (x * x for x in range(100) if x % 2 == 0)
g = islice(g, 5, 15) |
Ca serait tellement plus simple de pouvoir faire:
g = (x * x for x in range(100) if x % 2 == 0)[5:10] |
callable dans les slices
Si vous voulez tous les carrés des nombres au-dessus de 5, vous pouvez faire:
(x * x for x in numbres if x > 5) |
Mais si vous voulez tous les nombres à partir du moment où vous rencontrez un nombre au-dessus de 5 ?
from itertools import dropwhile
numbers = dropwhile(lambda x: x > 5, numbers)
(x * x for x in numbres) |
Alors certes, je ne suis pas pour transformer Python en Haskell et balancer des formules magiques comme:
(x * x for x in numbers[a -> a > 5]) |
Mais juste m’éviter l’import et pouvoir faire ça:
def start(x):
return x > 5
(x * x for x in numbers[start:]) |
Ca serait cool.
with var dans les expressions génératrices
Je suis hésitant sur cette feature car c’est très tentant de faire des one liners trop longs avec, mais:
(x.split()[1] for x in truc if x.split()[1] == 1) |
C’est con de faire 2 fois split
quand même.
(y for x in truc
with x.split()[1] as y
if y == 1) |
Bon, ça peut rapidement devenir illisible, donc à méditer.
async optionnel
Je cherche toujours à comprendre pourquoi async
est nécessaire.
Avec les générateurs, la présence de yield
fait détecter automatiquement la fonction comme fonction génératrice:
def stuff(): # pas de machin def
yield bidule # python comprends que c'est un générateur |
Avec await
, ça devrait être pareil:
def stuff():
await bidule # bim, c’est une coroutine ! |
Pas besoin de async
. Si on veut faire une coroutine sans un seul await
, il y a toujours @asyncio.coroutine
.
async
reste très utile pour async for
et async with
, mais perso j’aurais préféré avoir un await with
et un await for
et pas de async
.
On peut imaginer l’inverse aussi : tabler sur “explicit is bettern than implicit” et rajouter un gen def
pour les générateurs et obtenir la parité.
Après, le prenez pas mal hein. J’adore async
/await
. Vive asyncio
! Mais la syntaxe pourrait être plus cohérente, plus proche du comportement des générateurs, puisqu’un coroutine n’est qu’un générateur spécialisé.