Posted on :: Min Read :: Tags: ,

Contexte

J'ai suivi les UEs d'INPS et de PRSA dans lesquelles nous devions utiliser du Python. Mon stage de 2A était aussi axé sur du Python (appliqué aux réseaux de neurones).

De ces expériences, je me suis rendu compte à quel point Python est un langage facile d'accès et plutôt puissant si on apprend à bien s'en servir.

Je ne me revendique absolument pas comme un développeur confirmé de Python, mais j'ai retenu certaines astuces ou habitudes qui peuvent permettre de mener un projet engageant du Python avec rigueur.

Je vous présente ce que j'ai retenu (et ce que j'ai eu la foi de retranscrire ici).

Je pars du principe que vous avez déjà quelques rudiments sur certaines notions informatiques et en Python.

Discleamer

Ne considérez pas cette page comme un tuto absolu. Les astuces partagées sont discutables et peuvent ne pas être applicables pour votre projet. Faites la part des choses, et considérez les conseils que vous jugez pertinents.

Choisir un IDE

Avant toute chose, choisissons un environnement de développement.

Là, on va faire simple : faites ce que vous voulez !

Certains recommanderont d'utiliser PyCharm puisque c'est un IDE designé pour développer en Python. Personnellement, je ne l'ai jamais utilisé. Pour cause : la plupart des projets que j'ai mené jusque-là engageait d'autres langages de programmation.

De ce fait, soyez libres d'utiliser Vim/Neovim si vous appréciez le terminal, ou même VSCode/VSCodium.

Cas de VSCodium

J'utilise VSCodium pour développer en Python. Il existe plusieurs outils permettant d'obtenir un code Python organisé et simple.

Extensions

On commence tranquillement avec 6 extensions sur VSCodium très utiles.

  • Python - Coloration syntaxique et reconnaissance du langage
  • Python Debugger - Souvent de paire avec l'extension précédente.
  • Pylance - Bizzarement, la coloration syntaxique de l'extension Python est incomplète. Pour palier cette lacune, je vous recommande d'installer directement l'extension Pylance en consultant cette page.
  • autoDocstring - Python Docstring Generator - Il faut commenter ses fonctions ! Dans une logique de développeur, vous devez faire en sorte que chacune de vos fonctions soient compréhensibles par n'importe qui (ou, au minimum, chacun doit comprendre en lisant la documentation ce qu'un code fait). Cette extension permet de générer automatiquement la documentation en suivant un format défini. Je reviens dessus ci-après.
  • Pylint - Pylint est un outil open source conçu pour aider les développeurs à vérifier le code et sa qualité, ainsi que détecter les erreurs de programmation dans le langage Python et proposer des suggestions de refactoring simples. De plus, je vous invite à installer cet outil en passant par les tutos d'installation officiels présents ici. Vous pouvez l'utiliser en faisant dans un terminal pylint fichier.py.
  • Flake8 - Flake8 est un outil qui permet de lister facilement toutes les violations de normes (arbitraires mais cohérentes) au sein d'un script Python pour pouvoir les corriger. Vous pouvez aussi installer cet outil en suivant le guide d'installation.

Une fois que vous aurez ces extensions, vous partez déjà sur de très bonnes bases !

extensions.json

Allez, histoire de vous faire gagner une minute ou deux, vous pouvez aussi c/c ce fichier extensions.json et le mettre à la racine de votre projet dans le dossier .vscode. VSCodium vous suggérera d'installer les extensions précédentes.

{
    "recommendations": [
        // VSCodium \\

        // Python
        "ms-python.python",
        "ms-python.flake8",
        "ms-python.pylint",
        "ms-python.debugpy",
        "njpwerner.autodocstring",
        "ms-python.vscode-pylance", // https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/vscode-pylance/2023.6.40/vspackage

        // Other
        "gruntfuggly.todo-tree", // recommandation personnelle pour ajouter des TODO, FIXME, et autre
    ],
}

settings.json

Tout naturellement, il semble opportun de configurer à votre guise votre environnement de travail. Je vous joins ci-dessous un extrait d'un fichier settings.json que vous pouvez concaténer à votre configuration actuelle.

{
    // Formattage du texte général
    "files.trimFinalNewlines": true,
    "files.trimTrailingWhitespace": true,
    "files.insertFinalNewline": true,
    "editor.rulers": [
        79
    ],

    // Formattage de la docstring
    "autoDocstring.docstringFormat": "numpy",

    // Tests
    "python.testing.unittestArgs": [
        "-v",
        "-s",
        "./tests", // insérez le chemin relatif vers un dossier de tests si vous en avez un
        "-p",
        "run.py"
    ],
    "python.testing.pytestEnabled": false,
    "python.testing.unittestEnabled": true,
}

Quelques commentaires sur ces configurations :

  • files.trimFinalNewlines - Suppression de toutes les lignes vides à la fin du fichier (utile pour tout fichier, vous pouvez même l'ajouter dans votre configuration globale)
  • files.trimTrailingWhitespace - Suppression des espaces ou tabulations inutiles à la fin de chaque ligne (utile pour tout fichier, vous pouvez même l'ajouter dans votre configuration globale)
  • files.insertFinalNewline - Ajout d'un saut de ligne final à la fin du fichier pour respecter la convention POSIX (utile pour tout fichier, vous pouvez même l'ajouter dans votre configuration globale)
  • editor.rulers - Ajout d'un trait blanc à partir du 79e caractère pour chaque ligne de code. Il s'agit d'une convention de Flake8.
  • autoDocstring.docstringFormat - Choix du format de la docstring. Je vais y revenir ci-après.
  • python.testing.* - Configuration de la gestion des tests unitaires. Cela permet de les exécuter directement grâce à l'interface graphique de VSCodium en utilisant l'onglet de l'erlenmeyer à gauche de l'application (comme pour les tests unitaires en LAOB au S2).

Environnement virtuel

Quand vous travaillez sur un projet, il est préférable que tout le monde ait le même environnement de développement (version de Python similaire, dépendance de certains paquets...).

Je ne développerai pas davantage l'importance d'utiliser un environnement virtuel par projet, mais je vous invite tout de même à consulter le site de N. Dubray qui présente plus en détail cet outil fabuleux : https://dubrayn.github.io/PSA/pip.html.

À titre personnel, je vous recommande de faire au minimum de centraliser vos environnements virtuels sur votre laptop dans un dossier ~/.venv/dev/. Par exemple, pour le projet de PRSA, faites les commandes suivantes :

mkdir -p ~/.venv/dev
python3 -m venv ~/.venv/dev/prsa

Vous pourrez ensuite accéder et quitter votre environnement virtuel de la manière suivante :

# Activer votre environnement virtuel
source ~/.venv/dev/prsa/bin/activate
# ou
. ~/.venv/dev/prsa/bin/activate

# Désactiver votre environnement virtuel
deactivate

Si vous êtes motivés, le top du top serait d'ajouter dans le Makefile à la racine de votre projet une automatisation de la création de l'environnement virtuel pour vous assurer que tout le monde travaille dans les mêmes conditions. Voici un extrait de Makefile pour illustrer ce propos :

# Python variables
python = python3
venv := $(shell cat .venv-path 2>/dev/null || echo ~/.venv/dev/$$(date +'%Y%m%dT%H%M%S'))
requirements = requirements.txt
vpython = . $(venv)/bin/activate && python3

# install Python packages in a virtual environment
.PHONY : venv
venv : $(venv)/installed.txt

# install Python packages in a virtual environment
$(venv)/installed.txt: $(requirements) pyproject.toml
ifeq (,$(wildcard $(venv)))
	$(python) -m venv $(venv)
	echo $(venv) > .venv-path
endif
	$(vpython) -m pip install --upgrade --requirement $<
	$(vpython) -m pip install --editable .[ai]
	cp $< $@

requirements.txt contient la liste des modules Python à télécharger. Un exemple :

pip
build
twine
sphinx
sphinx-rtd-theme==2.0.0
myst-parser
pylint
flake8
matplotlib

pyproject.toml contient de nombreuses informations sur le projet. Vous pouvez le configurer en suivant la documentation associée.

La ligne $(vpython) -m pip install --editable .[ai] permet d'installer des dépendances supplémentaires indiquées dans la section [project.optional-dependencies] dans le fichier pyproject.toml.

[project.optional-dependencies]
ai = [
  "torch",
]

Aussi, si vous réutilisez ce Makefile, ajoutez .venv-path dans le .gitignore.

Documentation

Pour assurer la pérennité et la compréhension de votre code, il est vivement recommandé de le documenter.

Rappels

Pour rappel, la docstring Python s'insère entre la déclaration d'une méthode et le code source, comme illustrée ci-dessous :

def dummy(a, b):
    """Sum of two values."""
    return a + b

De plus, vous pouvez accéder à la documentation d'une méthode grâce à l'attribut __doc__.

>>> dummy.__doc__
'Sum of two values.'

Format

On retrouve plusieurs formats de docstring. Certains sont listés à cette page. Pour ma part, j'ai adopté le format NumPy. Retrouvez la documentation de ce format ici.

Voici un exemple d'utilisation de ce style de documentation :

def my_runner(directory, mute=True, **kwargs):
    """Execute a custom executable.

    Parameters
    ----------
    directory : str
        Path to the output directory.
    mute : bool, optional
        Print standard output.

    Keyword Arguments
    -----------------
    iteration : int
        Number of iteration of the executable.
    seed : int, optional
        Seed to randomize integers generation.

    Raises
    ------
    ErrorFromCustomRunner
        If my custom executable returned with an error.

    """
    # ...

Automatiser la construction de la documentation

Si vous avez étudié les UEs INPS et/ou PRSA, vous avez sûrement déjà entendu parler de Doxygen. Ce générateur de documentation est notamment utilisé pour documenter du code en C++. Pour notre code Python, je vous recommande d'utiliser Sphinx, qui est un autre générateur de documentation.

Cet outil vous permet de générer un site HTML en se basant sur la documentation de votre code (et quelques informations supplémentaires que vous aurez également fourni). Je ne détaillerai pas davantage comment configurer Sphinx. Je vous invite toutefois à consulter la documentation associée à cet outil.

Vous pouvez aussi générer automatiquement de la documentation grâce à la CI/CD Gitlab, un tuto pour le faire : https://bioinfo-fr.net/comment-creer-une-documentation-automatique-avec-doxygen-et-sphinx-en-ci-cd-gitlab.

Arborescence d'un projet

Il n'existe pas d'organisation universelle pour organiser un projet. Toutefois, certaines spécificités de Python tendent à nous rapprocher vers une organisation commune sur certains points.

Pour illustrer mes propos, je vais partir sur la base d'un projet appelé prsa. À la racine de ce projet, nous retrouvons tous les fichiers et dossiers cités précédemment. Tout naturellement, nous serons amenés à coder (je sais, vous attendiez ce moment :p).

Ma proposition est la suivante : à la racine du projet, vous créez un dossier src/prsa dans lequel figurera tout le code source du projet (réalisation d'un solveur, interaction avec une base de données...).

Dans ce dossier, on va retrouver deux fichiers qui nous intéresseront particulièrement : __init__.py et __main__.py.

Pour faire simple, le fichier __init__.py sert de base à la réalisation d'un paquet. En d'autres termes, il vous permet de faire import prsa et d'importer toutes les méthodes et classes qui découlent de ce dossier. Pour explorer plus en détail cet aspect sur la gestion des paquets, je vous invite à consulter la documentation officielle.

D'autre part, le fichier __main__.py sert à définir le paquet qui permet d'utiliser les fonctionnalités du projet en ligne de commande (comme python3 -m prsa). Je ne détaillerai pas comment cela fonctionne (si ce n'est qu'on a recours à la librairie standard argparse pour ajouter des arguments/options en entrée), donc je vous invite à consulter des exemples en ligne et la documentation officielle.


Une fois ces fichiers prêts, il ne vous reste plus à attaquer le code source. Pour ma part, ayant réalisé pendant mon stage de 2A une chaîne de calcul permettant d'automatiser l'exécution successive de deux programmes codés dans des langages différents (disons prog1 et prog2), j'avais alors deux dossiers type src/prsa/prog1 et src/prsa/prog2 contenant les codes sources de ces deux programmes, ainsi qu'un dossier src/prsa/core contenant le code source assurant la chaîne de calcul.

Code source

On peut faire beaucoup de projets en Python sur des thèmes différents. Je ne vais pas pouvoir tout balayer, d'autant plus que chaque type de projet nécessite une mise en place différente. De ce fait, je vais rester assez général sur les conseils pour vous permettre de réaliser un code source de qualité.

Module

Choix des modules

Privilégiez les modules de la librairie standard de Python. Une grande partie d'entre elles fera exactement ce que vous attendez. Pour n'en citer que quelques-unes :

  • os, shutil - Manipulation de fichiers/dossiers
  • importlib - Accès à des ressources d'un paquet (importlib.resources)
  • hashlib - Unicité des fichiers
  • subprocess - Exécution de commande bash
  • datetime, time, traceback, logging - Versionnage des fichiers créés et suivi des activités
  • json - Lecture et écriture de fichiers JSON (équivalence avec les dictionnaires Python)
  • configparser - Lecture de fichiers .conf et .ini
  • unittest - Réalisation de tests unitaires

Aussi, selon votre projet, il est naturel que vous devrez utiliser d'autres bibliothèques externes (numpy pour le calcul scientifique, matplotlib pour la réalisation de graphiques, torch pour les réseaux de neurones...).

Faites donc correctement la part des choses pour ensuite déterminer ce dont vous aurez réellement besoin ou non.

Importation des modules

Là aussi, vous êtes libres de faire comme vous voulez. J'ai une préférence sur le fait de limiter le nombre de méthodes à importer.

Par exemple, si je souhaite importer un petit nombre de méthodes d'un module, à titre d'exemple, j'adopterai la syntaxe suivante :

from time import sleep, time

start = time()
sleep(3)
print(time() - start)

En particulier, cela permet d'éviter des chevauchements au niveau du nom de certaines méthodes entre deux paquets (méthode open de tarfile et méthode open standard, par exemple).

Dans ce cas, on peut choisir aussi de changer le nom de la méthode quand on veut l'appeler. Un exemple :

from tarfile import open as open_tar

Toutefois, si vous pensez que vous serez amenés à utiliser plusieurs méthodes ou sous-modules d'un module (comme par exemple les fonctionnalités de torch), faire import torch suffit amplement.

Tests unitaires

On utilise la librairie standard unittest pour réaliser nos tests unitaires.

Vous pouvez, par exemple, ajouter vos fichiers de tests associés dans un dossier tests présent à la racine du projet.

Sans entrer dans les détails de la conception des tests unitaires, notez qu'on doit déclarer une classe qui hérite de la classe TestCase issue de unittest.

Chaque nom des méthodes de tests doivent être préfixés de test_.

Je vous invite à consulter la documentation officielle pour voir comment réaliser des tests unitaires efficaces.

CI

Aussi appelé l'intégration continue Gitlab, cet outil est intéressant pour permettre à un runner d'exécuter du code.

Cette fonctionnalité est intéressante et peut être exploité de diverses manières. Il suffit de réaliser un fichier .gitlab-ci.yml à la racine de votre projet. Pour le moment, notez que vous pouvez automatiser l'exécution de vos tests unitaires à chaque fois que vous déployez votre code sur le dépôt en ligne. De cette manière, si vous modifiez le code source et observez une erreur, cela vous permet d'anticiper une potentielle faille ou un dysfonctionnement de votre code qui peut être, par la suite, plus difficile à corriger.

Encore une fois, consultez la documentation et des exemples d'utilisation de cette fonctionnalité pour l'adapter à votre projet.

UX/UI

Quand j'évoque l'UX/UI (User Experience/User Interface), je pense ici à la façon dont un utilisateur quelconque va vouloir utiliser votre code.

Notre objectif est de simplifier l'utilisation d'un code donné.

Pour ce faire, une première habitude est de toujours fournir un fichier README.md pour indiquer à l'utilisateur comment utiliser votre programme sans avoir besoin de modifier le code source.

Allons plus loin : supposons que votre programme est fait pour réaliser des simulations avec des paramètres différents. Si vous avez un certain nombre de paramètres à changer, vous ne les spécifierez pas dans la ligne de commande associée. de même, vous ne changerez pas le code source pour chaque simulation (un utilisateur n'est pas censé avoir besoin de faire ça).

Dans ce cas, vous avez deux options.

Fichier INI

Ce format de fichiers est adapté pour permettre à un utilisateur de donner des valeurs d'entrée. Le format de ce type de fichiers est relativement simple, puisqu'il fonctionne à base de sections et de paires clé-valeur. On renomme ces fichiers avec l'extension .ini.

Retrouvez un exemple d'utilisation de ce fichier avec la documentation officielle de configparser.

Fichier JSON

Pareillement, ce format de fichiers est utilisé pour donner des inputs, mais il peut aussi être intéressant pour donner des informations concernant l'exécution d'un programme (exécution en cours, temps d'exécution, rappel des données utilisées...). À raison, le passage entre les dictionnaires Python et le format des fichiers JSON est plutôt naturel sous réserve d'utiliser le module json.

Pareillement, je vous invite à consulter la documentation officielle.

Conclusion

Je vous ai présenté plusieurs outils et fonctionnalités vous permettant de développer dans de bonnes conditions votre projet Python.

Cette page est clairement incomplète sur de nombreux aspects. Si vous souhaitez vous intéresser au sujet, il revient à vous d'effectuer les recherches nécessaires.

Enfin, je vous recommande vivement de consulter le cours de N. Dubray disponible ici : https://dubrayn.github.io. Son cours est une excellente ressource (avec des exemples à l'appui) pour développer et approfondir certaines idées abordées ici.

En fin de compte, Python n'est pas un mauvais langage de programmation quand on découvre comment s'en servir !


Bon développement ! <3