De temps en temps, on me demande des conseils d'articles et de tutoriels pour connaître l'état de l'art en Python sur tel ou tel sujet. Pas si facile quand on baigne dedans et qu'on a déjà digéré l'essentiel des bonnes pratiques depuis des années. Et puis ça bouge. Alors voici ma version 2019 !
Utiliser Python 3
Vous découvrez Python et on vous a pris la tête avec Python 2 vs Python 3 ? Pour 2019, c'est plus simple : partez directement sur Python 3, au minimum 3.4. Si vraiment on vous demande d'être compatible Python 2, le portage ne sera pas bien compliqué à faire après coup.
Sachez tout de même que Python 2 ne sera plus maintenu à la fin de cette année 2019. Plus de corrections de bug ni de faille de sécurité. N'investissez pas trop dans Python 2 !
Isoler son projet avec Poetry
Pour protéger votre système, pour recommencer un projet ou simplement participer à plusieurs projets, il faut isoler votre projet. Contrairement à Node ou Ruby, ce n'est pas le cas par défaut avec Python. Mais il y a le choix ! Pour 2019, je vous conseille poetry. Cet outils va gérer pour vous :
- la définition du projet : nom, version, licence, etc.
- le choix de la version de Python.
- la déclarations et la résolution des dépendances.
- l'isolation du projet.
- la publication sur PyPI.
À coupler avec pyenv pour installer différentes versions de Python.
Si vous devez livrer un paquet deb ou rpm, poetry génère pour vous
systématiquement un sdist à l'ancienne, avec un setup.py
. Donc pas de soucis
de compatibilité !
Valider la syntaxe et le style avec flake8
J'aime black, mais flake8 est encore indispensable. flake8 est mieux intégré aux éditeurs et black ne détecte pas les typos dans les noms de variables, c'est rédhibitoire.
On me répondra qu'un formatteur de code n'est pas un validateur. Certes. Je réponds que je ne veux qu'un outils d'analyse statique du code pour valider et formater.
Utiliser au choix le framework web Flask ou Sanic
J'ai deux frameworks de prédilection pour 2019. Pour un projet ambitieux, avec de fortes exigences sur le temps de réponse et la concurrence, utilisez la programmation asynchrone avec le framework Sanic.
Si vous n'êtes pas sûr, n'hésitez plus et utilisez Flask. Vous ne regretterez pas.
Pas d'ORM ou SQLAlchemy
On cherche souvent un ORM. Mais d'abord, avez-vous besoin d'un ORM ? Le standard DB-API de Python permet déjà de se connecter à différents SGBD et de sécuriser la génération des requêtes SQL. Épargnez-vous l'apprentissage d'une surcouche entre votre SGBD et votre code en utilisant directement psycopg2.
Si vous souhaitez être portable entre les différents dialectes SQL au risque de vous réduire au plus petit dénominateur commun ou si un ORM vous semble indispensable, optez pour SQLAlchemy sans hésitez. Vous pouvez utiliser l'abstraction de dialectes SQL sans la partie ORM.
Pour interroger Postgres en asynchrone, asyncpg est très efficace, sécurisé et proche de Postgres.
La bibliothèque standard pour les logs
Utilisez logging de la bibliothèque standard et configurez les logs en premier.
Ne jamais utiliser print()
ni sys.stderr.write()
. En ayant toujours les
traces fonctionnelles, une fonction n'a plus à demander si elle va être utilisée
avant ou après la configuration des traces. On peut toujours reconfigurer les
traces plus tard, par exemple après le chargement de la configuration.
Comment gérer les exceptions ?
Sujet délicat ! Une mauvaise gestion d'erreur frustrera vos utilisateurs, et vous d'abord. La vue d'une pile d'exécution génère du stress et démotive. C'est exactement le pendant CLI des popups d'erreur de Windows au démarrage. D'abord, distinguons trois ensembles d'exceptions :
- Les signaux tels
KeyboardInterrupt
(SIGINT
) ouSystemExit
(SIGTERM
). J'ajoute ici la sortie de debugger (bdb.BdbQuit
). À gérer uniquement à la base du programme. - Les erreurs connues : mauvaise configuration, délai d'attente expiré, etc. Afficher seulement le message à l'utilisateur pour lui dire d'abord ce qui ne va pas, éventuellement le guider pour résoudre le problème. Si c'est critique, arrêter le logiciel avec un code d'erreur positif !!
- Les erreurs inattendues. Ce sont des bugs ! Dans ce cas là, afficher clairement "Erreur inattendue:" et la pile d'exécution. Conclure en assumant que c'est un bug et pointer vers une adresse de contact pour remonter le bug (formulaire web, adresse électronique, etc.)
Avec cette nomenclature, la gestion d'erreur devient plus évidente. Voici un petit exemple :
class ErreurConnue(Exception):
# Permet d'arrêter le programme de n'importe où.
def __init__(self, message, exit_code=1):
super(ErreurConnue, self).__init__(message)
self.exit_code = exit_code
def main():
# Le coeur du programme.
...
try:
main()
# Si tout se passe bien, terminer avec 0.
exit(0)
except pdb.bdb.BdbQuit:
logger.info("Fin de pdb.")
except ErreurConnue as e:
logger.critical("%s", e)
exit(e.exit_code)
except Exception:
logger.exception("Erreur inconnue:")
logger.error(
"Merci de remonter l'erreur avec la trace à "
"https://gitlab.com/mon/projet/issues.",
)
# Dans tout autre cas, annoncer l'erreur aux autre programmes.
exit(os.EX_SOFTWARE)
Avec ça, votre logiciel est poli avec le développeur, l'utilisateur et les autres programmes. Ce n'est pas grave de faire des erreurs. Ce qui est dommage, c'est que vos utilisateurs s'habituent à les contourner sans vous informer des améliorations à faire.
Enfin, évitez les try-except partout pour masquer les erreurs. Un try-except doit servir à enrichir l'erreur d'information de contexte ou tracer le pourquoi d'une décision.
pytest, testinfra et GitLab CI pour tester
Sans hésitation, utilisez pytest pour exécuter vos tests et gérer les fixtures. Un point où pytest dépasse tout ses concurrents, ce sont les fixtures composables. Plutôt que de se perdre dans l'héritage multiple, mieux vaut laisser pytest gérer les dépendances de fixtures. Au premier abord, cela semble de la magie noire. Mais c'est tellement stable et expressif qu'on s'y fait très vite.
Python reste un bon langage pour rédiger des tests d'intégration également. Pour rédiger des tests fonctionnels, j'utilise au choix plumbum, sh ou testinfra.
Pour aller jusqu'au bout, je vous conseille d'exécuter vos tests avec GitLab CI et donc d'héberger votre code sur GitLab. En 2018, GitLab.com a progressé sur les performances et la stabilité. Gageons que 2019 sera meilleure encore. Pour les projets sur GitHub, ce sera CircleCI. Au pire, tout sauf Jenkins.
Quelques conseils de conceptions
Il ne suffit pas de bien choisir ses outils. Le coût et la démotivation d'un projet immaintenable sont tellement fréquents et lourds que je ne peux m'empêcher de vous donner quelques conseils pour augmenter la pérennité de votre projet Python. Le langage Python est pensé pour cela, à vous de vous en servir dans ce sens pour en profiter à fond !
Connaissez-vous le concept de Clean Architecture ? A priori, cela ressemble à de grandes théories. En réalité vous pouvez appliquer certains principes dans un simple script de 100 lignes. Brandon Rhodes de Dropbox a présenté de nombreuses fois ces principes de manière très accessible sous le titre « Hoist your I/O », c'est-à-dire « Remontez vos entrées-sorties » (sous-entendu : au début de la pile d'exécution). Vous pouver voir un enregistrement à la PyCon 2015.
Plus centré sur Python, Raymond Hettinger − core-dév de Python − explique avec beaucoup de pédagogie comment l'interprêteur Python aide à concevoir des API plus faciles à utiliser et à maintenir en traçant une frontière précise entre la bibliothèque et l'application. Les générateurs et les gestionnaires de contexte permettent de gérer les contraintes de la bibliothèque sans sacrifier la lisibilité du code de l'application. Sa présentation s'appelle Beyond PEP8. PEP8 vous aide sur le style de votre code. Beyond PEP8 vous conseillera sur l'UX de votre API.
Pour aller plus loin
Si vous cherchez une bibliothèque ou un conseil, commencez par chercher dans vinta/Awesome Python. Sur ce, à vos claviers et bonne année 2019 avec Python !