<!--
  canonical-url: https://www.avonture.be/fr/blog/python-qa
  published:     2024-12-30
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Python - Les outils de qualité de code

> Améliorez la qualité de votre code Python ! Découvrez 9 outils d'analyse statique essentiels comme Pylint, Black et mypy, ainsi qu'un workflow QA optimal pour un code plus propre et sans bugs.

---
![Python - Les outils de qualité de code](/img/v2/clean_code.webp)

**TL;DR**

Cet article recense la chaîne d'outils de qualité de code Python de l'auteur — Pylint, Autoflake, isort, Vulture, pydocstyle, mypy, Pyright, Black et Prospector, plus Ruff comme alternative tout-en-un ultra rapide — avec, pour chacun, la commande CLI exacte et le fichier de configuration. Ils sont enchaînés, du plus rapide/plus fondamental en premier, dans une cible `make qa` qui s'arrête au premier échec et n'affiche « CONGRATULATIONS » que si toutes les vérifications passent.

Si vous êtes un programmeur qui se respecte, vous ne pouvez pas développer sans outils d'analyse de code. Pour les programmeurs PHP, vous en connaissez déjà beaucoup (`rector`, `phpstan`, `phan`, `phpcs`, ...). Voyez mes articles précédents [Rector 1.0.0, my friend, my coach](/blog/php-rector) et [Docker image that provides static analysis tools for PHP](/blog/php-jakzal-phpqa).

Et pour Python ?

## Le résultat : une commande, neuf vérifications, un seul point d'échec {#the-result-one-command-nine-checks-one-clear-failure-point}

`make qa` enchaîne Pylint, Autoflake, isort, Vulture, pydocstyle, mypy, Pyright, Black et Prospector — l'outil le plus rapide d'abord — et s'arrête au tout premier échec. Voici à quoi ressemble une exécution propre :

```bash
$ make qa

 Step  Tool                          Run?      Techno    Description
--------------------------------------------------------------------------------------------------------------
 1/9  RUNNING make Pylint            Lint python scripts using Pylint (https://pypi.org/project/pylint/)
Your code has been rated at 10.00/10 (previous run: 10.00/10, +0.00)

 2/9  RUNNING make Autoflake         Detect unused variables and unused imports (https://pypi.org/project/autoflake/)

 3/9  RUNNING make isort             Sort import statement using isort (https://pycqa.github.io/isort/)

 4/9  RUNNING make Vulture           Find dead Python code (https://github.com/jendrikseipp/vulture)

 5/9  RUNNING make Pydocstyle        pydocstyle is a static analysis tool for checking compliance with Python docstring conventions (https://www.pydocstyle.org/en/stable/)

 6/9  RUNNING make Mypy              Mypy is a program that will type check your Python code (https://github.com/python/mypy/)
Success: no issues found in 12 source files

 7/9  RUNNING make Pyright           Pyright is a full-featured, standards-based static type checker for Python (https://github.com/microsoft/pyright)
0 errors, 0 warnings, 0 informations

 8/9  RUNNING make Black             Format the script using Black (https://black.readthedocs.io/en/stable/)
All done! ✨ 🍰 ✨
12 files left unchanged.

 9/9  RUNNING make Prospector        Inspects Python source files and provides information about type and location of classes, methods, ... (https://github.com/prospector-dev/prospector/)
Messages Found: 0

CONGRATULATIONS!!!
Not the slightest error or notice detected, that's .. amazing!
🥳 🎉 🎊 🤩 🕺 💃 👏
```

Rien de vert à chercher : le silence (ou une note comme le `10.00/10` de Pylint) signifie que la vérification est passée. Il en rate une seule et la chaîne s'arrête là — pas de **CONGRATULATIONS**, et une seule chose à corriger avant de relancer.

## Pourquoi ça fonctionne {#why-it-works}

* Les outils s'exécutent du plus rapide/plus fondamental en premier : inutile de lancer un vérificateur de types lent si le code ne se parse même pas — c'est pourquoi Pylint est 1/9 et Autoflake, qui nettoie les imports, est 2/9.
* La chaîne s'arrête au premier échec : une exécution de `make qa`, une chose claire à corriger, pas neuf rapports à recouper.
* `make qa` est un point d'entrée unique : c'est la même cible que vous lancez à la main, que lance un hook pre-commit et que lance la CI — j'y reviens en conclusion.

## Mise en place {#installation}

Je suis un grand fan des outils statiques de qualité de code, et voici ma courte liste, câblée ensemble avec une action [makefile](/blog/tags/makefile) appelée `qa` :

**makefile**

```
# cspell:disable
.PHONY: qa
qa:
	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " Step  Tool" "Run?" "Techno" "Description"
	@echo "--------------------------------------------------------------------------------------------------------------"
	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 1/9  RUNNING make Pylint" "Lint python scripts using Pylint (https://pypi.org/project/pylint/)"
	@pylint . --rcfile .config/.pylintrc

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 2/9  RUNNING make Autoflake" "Detect unused variables and unused imports (https://pypi.org/project/autoflake/)"
	@autoflake --remove-unused-variables --remove-all-unused-import --recursive .

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 3/9  RUNNING make isort" "Sort import statement using isort (https://pycqa.github.io/isort/)"
	@isort .

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 4/9  RUNNING make Vulture" "Find dead Python code (https://github.com/jendrikseipp/vulture)"
	@vulture --min-confidence 100 .

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 5/9  RUNNING make Pydocstyle" "pydocstyle is a static analysis tool for checking compliance with Python docstring conventions (https://www.pydocstyle.org/en/stable/)"
	@pydocstyle --config=.config/.pydocstyle

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 6/9  RUNNING make Mypy" "Mypy is a program that will type check your Python code (https://github.com/python/mypy/)"
	@mypy --config-file .config/.mypy.ini .

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 7/9  RUNNING make Pyright" "Pyright is a full-featured, standards-based static type checker for Python (https://github.com/microsoft/pyright)"
	@pyright --project .config/pyright.json

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n" " 8/9  RUNNING make Black" "Format the script using Black (https://black.readthedocs.io/en/stable/)"
	@black --config .config/black.toml .

	@printf "\e[1;37m%-35s%-10s%-10s\e[m%s\n\n" " 9/9  RUNNING make Prospector" "Inspects Python source files and provides information about type and location of classes, methods, ... (https://github.com/prospector-dev/prospector/)"
	@prospector . --profile .config/prospector.yaml --pylint-config-file .config/.pylintrc

	@printf "%s\n" "CONGRATULATIONS!!!"
	@printf "%s\n" "Not the slightest error or notice detected, that's .. amazing!"
	@printf "%s\n" "🥳 🎉 🎊 🤩 🕺 💃 👏"
```

Dès qu'une erreur est détectée, le script s'arrête. Vous ne verrez le message **CONGRATULATIONS**, exactement comme montré ci-dessus, que si les neuf vérifications réussissent.

## Plus de démos : les neuf outils, un par un {#more-demos-the-nine-tools-one-by-one}

### 1. Pylint, l'analyseur statique {#1-pylint}

> <https://pypi.org/project/pylint/>
>
> [Extension VSCode](https://marketplace.visualstudio.com/items?itemName=ms-python.pylint)
>
> Pylint est un analyseur de code statique pour Python 2 ou 3. La dernière version supporte Python 3.9.0 et supérieur.
>
> Pylint analyse votre code sans réellement l'exécuter. Il cherche les erreurs, applique un standard de codage, repère les code smells et peut suggérer comment le code pourrait être refactorisé.

Commençons par le commencement : assurez-vous que votre code Python n'a pas d'erreurs de syntaxe — pas de mauvaise indentation, pas de `:` oublié à la fin d'une instruction de contrôle (comme un `if` ou un `for`).

Je le lance comme ceci : `pylint . --rcfile .config/.pylintrc`.

**.config/.pylintrc**

```
[MASTER]
; Pickle collected data for later comparisons.
persistent=yes

; List of plugins (as comma-separated values of python modules names) to load,
; usually to register additional checkers.
load-plugins=
    pylint.extensions.check_elif,
    pylint.extensions.bad_builtin,
    pylint.extensions.docparams,
    pylint.extensions.for_any_all,
    pylint.extensions.set_membership,
    pylint.extensions.code_style,
    pylint.extensions.overlapping_exceptions,
    pylint.extensions.typing,
    pylint.extensions.redefined_variable_type,
    pylint.extensions.comparison_placement,

; Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
; number of processors available to use.
jobs=0

; When enabled, pylint would attempt to guess common misconfiguration and emit
; user-friendly hints instead of false-positive error messages.
suggestion-mode=yes

; Minimum supported python version
py-version = 3.13.0

; Specify a score threshold to be exceeded before program exits with error.
fail-under=9.50

[FORMAT]
max-line-length=120
```

### 2. Autoflake, le nettoyage des imports inutilisés {#2-autoflake}

> <https://pypi.org/project/autoflake/>
>
> Autoflake supprime les imports et les variables inutilisés du code Python. Il s'appuie sur pyflakes pour cela.
>
> Par défaut, Autoflake ne supprime que les imports inutilisés des modules faisant partie de la bibliothèque standard. (D'autres modules peuvent avoir des effets de bord rendant leur suppression automatique risquée.) La suppression des variables inutilisées est également désactivée par défaut.
>
> Autoflake supprime aussi par défaut les instructions pass inutiles.

Je le lance comme ceci : `autoflake --remove-unused-variables --remove-all-unused-import --recursive .`

### 3. isort, le tri des imports {#3-isort}

> <https://pycqa.github.io/isort/>
>
> [Extension VSCode](https://marketplace.visualstudio.com/items?itemName=ms-python.isort)
>
> `isort` trie vos imports, pour que vous n'ayez pas à le faire.
>
> isort est un utilitaire / une bibliothèque Python qui trie les imports par ordre alphabétique et les sépare automatiquement en sections et par type. Il fournit un utilitaire en ligne de commande, une bibliothèque Python et des plugins pour divers éditeurs afin de trier rapidement tous vos imports. Il nécessite Python 3.7+ pour fonctionner mais sait aussi formater du code Python 2.

Je le lance comme ceci : `isort .`

Note : j'ai aussi configuré mon VSCode avec ce réglage `"python.sortImports.args": ["--profile", "black"]` pour que les instructions d'import soient triées automatiquement pendant que je code.

### 4. vulture, la détection de code mort {#4-vulture}

> <https://github.com/jendrikseipp/vulture>
>
> Vulture trouve le code inutilisé dans les programmes Python. C'est utile pour nettoyer et trouver des erreurs dans de grandes bases de code. Si vous lancez Vulture à la fois sur votre bibliothèque et sur votre suite de tests, vous pouvez repérer le code non testé.
>
> À cause de la nature dynamique de Python, les analyseurs statiques comme Vulture risquent de manquer du code mort. Par ailleurs, du code appelé uniquement de manière implicite peut être signalé comme inutilisé. Malgré tout, Vulture peut être un outil très utile pour une meilleure qualité de code.

Je le lance comme ceci : `vulture --min-confidence 100 .`

> **Danger:**
>
> Attention avec Vulture : son algorithme détecte beaucoup de faux positifs, c'est pourquoi, dans mon processus d'automatisation, j'ai utilisé `--min-confidence 100` pour être sûr de n'avoir **que** du vrai code / de vraies variables inutilisés.

Note : j'ai aussi configuré mon VSCode avec les réglages ci-dessous pour que, pendant que je code, VSCode me signale les éléments inutilisés et que je puisse réagir immédiatement.

**.vscode/settings.json**

```json
"python.analysis.diagnosticSeverityOverrides": {
    "reportUnusedClass": "information",
    "reportUnusedFunction": "information",
    "reportUnusedImport": "information",
    "reportUnusedVariable": "information"
},
```

### 5. pydocstyle, la qualité des docstrings {#5-pydocstyle}

> <https://www.pydocstyle.org/en/stable/>
>
> `pydocstyle` est un outil d'analyse statique qui vérifie le respect des conventions de docstrings Python.

Cet outil vérifie la qualité de vos commentaires, comme les descriptions de vos fonctions et, par exemple, s'assure que si vous avez une fonction avec deux arguments, votre description explique les deux arguments (il y a un contrôle sur le nom et sur le type).

Je le lance comme ceci : `pydocstyle --config=.config/.pydocstyle`

**.config/.pydocstyle**

```
[pydocstyle]
ignore = D100,D203,D205,D212,D213,D400,D404,D406,D407,D413,D415
match = .*\.py
```

> **Caution:**
>
> Le repository GitHub de pydocstyle a été archivé en novembre 2023 et n'est plus activement maintenu. La communauté recommande de migrer vers **Ruff** (voir la section « Sous le capot » ci-dessous), qui couvre des vérifications de docstrings équivalentes via son jeu de règles compatible `pydocstyle`.

### 6. mypy, le typage statique {#6-mypy}

> <https://github.com/python/mypy/>
>
> Mypy est un vérificateur de types statique pour Python.
>
> Les vérificateurs de types aident à s'assurer que vous utilisez correctement les variables et les fonctions de votre code. Avec mypy, ajoutez des annotations de type (PEP 484) à vos programmes Python, et mypy vous avertira quand vous utiliserez ces types de façon incorrecte.
>
> Python est un langage dynamique : d'ordinaire, vous ne voyez les erreurs de votre code qu'au moment de l'exécuter. Mypy est un vérificateur statique, il trouve donc les bugs de vos programmes sans même les exécuter !

Je le lance comme ceci : `mypy --config-file .config/.mypy.ini .`

**.config/.mypy.ini**

```ini
[mypy]
show_error_codes = true

; First we turn on *all the checks*, and then we turn off those that are too annoying.
strict = True

cache_dir = /tmp/mypy

no_implicit_optional = true

strict_equality = true
warn_redundant_casts = true
warn_unused_ignores = true

python_version = 3.13
```

### 7. Pyright, l'autre vérificateur de types {#7-pyright}

> <https://github.com/microsoft/pyright>
>
> Pyright est un vérificateur de types statique complet et basé sur les standards pour Python. Il est conçu pour la performance et peut être utilisé sur de grandes bases de code Python.

Je l'utilise comme ceci : `pyright --project .config/pyright.json`

**.config/pyright.json**

```json
{
    "include": [ "." ],
    "pythonVersion": "3.13",
    "pythonPlatform": "Linux"
  }
```

### 8. Black, le formatage automatique {#8-black}

> <https://black.readthedocs.io/en/stable/>
>
> [Extension VSCode](https://marketplace.visualstudio.com/items?itemName=ms-python.black-formatter)
>
> En utilisant Black, vous acceptez de céder le contrôle sur les détails du formatage manuel. En échange, Black vous donne de la vitesse, du déterminisme et la liberté de ne plus subir les remarques de pycodestyle sur le formatage. Vous économiserez du temps et de l'énergie mentale pour des sujets plus importants.
>
> Black accélère la revue de code en produisant les diffs les plus petits possibles. Le code passé à Black a la même allure quel que soit le projet que vous lisez. Le formatage devient transparent au bout d'un moment et vous pouvez vous concentrer sur le contenu.

Je l'utilise comme ceci : `black --config .config/black.toml .`

**.config/black.toml**

```toml
[tool.black]

# The same as in prospector.yaml and .pylintrc
line-length = 120

# We'll format the code for Python 3.13.0; we can add multiple versions (the ones supported by
# the script, all versions separated by a comma like in ['py312', 'py313'] (run "black --help" to get them)
target-version = ['py313']
```

### 9. prospector, l'inspection globale {#9-prospector}

> <https://github.com/prospector-dev/prospector/>
>
> Inspecte les fichiers source Python et fournit des informations sur le type et l'emplacement des classes, méthodes, etc.

Je l'utilise comme ceci : `prospector . --profile .config/prospector.yaml --pylint-config-file .config/.pylintrc`

**.config/prospector.yaml**

```yaml
strictness: high
test-warnings: true
doc-warnings: false

pycodestyle:
  options:
    max-line-length: 120

  disable:
    - D100
    - D203
    - D205
    - D212
    - D213
    - D400
    - D404
    - D406
    - D407
    - D413
    - D415

bandit:
  run: true

dodgy:
  run: true

pyflakes:
  run: true

mccabe:
  run: true
```

## Sous le capot (passez votre chemin si vous voulez juste l'utiliser) {#under-the-hood-skip-this-if-you-just-want-to-use-it}

### En bonus - Ruff, un remplaçant potentiel pour plusieurs outils {#extra---ruff-a-potential-replacement-for-several-tools}

> <https://github.com/astral-sh/ruff>
>
> Ruff, un linter et formateur de code Python extrêmement rapide, écrit en Rust.
>
> [Extension VSCode](https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff)

Pour l'instant, j'ai trop peu d'expérience avec Ruff, je préfère donc l'ajouter en **bonus**.

La documentation indique qu'il est extrêmement rapide et je dois avouer que, la première fois que je l'ai lancé, je pensais que rien ne s'était passé (vraiment trop rapide). J'ai introduit une erreur volontaire dans mon code et elle a été correctement détectée : oui, l'outil est extrêmement rapide.

D'après la documentation de Ruff, cet outil peut remplacer complètement Pylint (le linter), Autoflake (suppression des imports/variables inutilisés) et Black (l'outil de formatage), mais il ne remplace pas MyPy ni Pyright (doc).

Je l'utilise comme ceci : `ruff format --cache-dir /tmp/ruff --config .config/pyproject.toml .` et `ruff check --cache-dir /tmp/ruff --config .config/pyproject.toml .`

**.config/pyproject.toml**

```toml
[tool.ruff]

exclude=[]

# The same as in prospector.yaml, .pylintrc, black.toml and pyproject.toml
line-length = 120
indent-width = 4
```

## Conclusion {#conclusion}

Neuf outils, une commande, un seul point d'échec — `make qa` transforme le « est-ce que j'ai pensé à tout vérifier ? » en une exécution déterministe unique, la vérification la plus rapide/fondamentale d'abord, qui s'arrête dès que quelque chose cloche.

Le lancer à la main suppose quand même que vous y pensiez. Deux façons de l'automatiser : [Git - pre-commit-hooks](/blog/git-precommit) refuse le commit quand une vérification échoue, et [Dagger.io - Using dagger to automate your CI workflows](/blog/dagger-python) exécute exactement les mêmes étapes en local et dans votre CI.
