Débuter avec Python en 2019

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) ou SystemExit (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(KnownError, 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 !

Bonne année 2019 !