Aller au contenu
Développement medium

Ruff : le linter et formateur Python ultra-rapide

20 min de lecture

logo ruff

Le problème : trop d’outils pour un code propre

Section intitulée « Le problème : trop d’outils pour un code propre »

Si vous travaillez sur un projet Python, vous connaissez probablement cette situation : avant chaque commit, vous devez lancer plusieurs outils pour garantir la qualité du code.

  • Flake8 pour détecter les erreurs et violations de style
  • Black pour formater le code automatiquement
  • isort pour trier les imports
  • Pylint pour une analyse plus approfondie
  • pyupgrade pour moderniser la syntaxe

Résultat : votre hook pre-commit prend 15-20 secondes. Sur un gros projet, c’est encore pire. Certains développeurs finissent par désactiver ces vérifications localement et comptent sur la CI pour attraper les problèmes — ce qui ralentit tout le monde.

Ruff résout ce problème en combinant tous ces outils en un seul, écrit en Rust, qui est 100 à 200 fois plus rapide que Flake8.

Ce guide vous accompagne pour :

  1. Comprendre pourquoi Ruff change la donne pour la qualité du code Python
  2. Installer Ruff et le configurer pour votre projet
  3. Choisir les règles adaptées à vos besoins
  4. Formater votre code comme Black, mais instantanément
  5. Corriger automatiquement les problèmes détectés
  6. Intégrer Ruff dans VS Code, pre-commit et GitHub Actions

Pourquoi Ruff plutôt que Flake8, Black et isort ?

Section intitulée « Pourquoi Ruff plutôt que Flake8, Black et isort ? »

Avant d’installer Ruff, comprenons ce qui le rend différent.

Quand vous lancez Flake8 sur un projet de 100 000 lignes, vous attendez 10 à 20 secondes. Black peut prendre plusieurs minutes sur un gros projet. Ces délais s’accumulent et cassent votre concentration.

Avec Ruff, la même analyse prend moins d’une seconde. Cette différence vient de plusieurs facteurs :

  • Code Rust : pas d’interpréteur Python à démarrer, exécution native
  • Analyse parallèle : Ruff utilise tous les cœurs de votre processeur
  • Cache intelligent : les fichiers non modifiés ne sont pas réanalysés

Avant Ruff, maintenir un code Python propre nécessitait plusieurs outils avec chacun sa configuration :

  • Flake8 avec son .flake8 ou setup.cfg
  • Black avec sa section dans pyproject.toml
  • isort avec une autre section dans pyproject.toml
  • Pylint avec son .pylintrc

Ruff remplace tout ça avec une seule configuration dans pyproject.toml. Plus de conflits entre outils, plus de versions incompatibles, une seule documentation à consulter.

Ruff est conçu pour être un remplacement transparent. Il comprend les mêmes codes d’erreur que Flake8, il formate comme Black (avec la même longueur de ligne par défaut de 88 caractères), et il trie les imports comme isort.

Si vous avez des commentaires # noqa ou # type: ignore dans votre code, Ruff les respecte. La migration est progressive : vous pouvez commencer par remplacer un outil à la fois.

Ruff s’installe de plusieurs façons selon votre environnement.

La méthode la plus courante. Si vous utilisez uv (recommandé), ajoutez Ruff comme dépendance de développement :

Fenêtre de terminal
uv add --dev ruff

Avec pip classique :

Fenêtre de terminal
pip install ruff
Fenêtre de terminal
ruff --version

Vous devriez voir quelque chose comme ruff 0.8.x.

Ruff a deux modes principaux : linter (vérification) et formatter (formatage). Voyons comment les utiliser.

La commande ruff check analyse votre code et signale les problèmes :

Fenêtre de terminal
ruff check .

Le . signifie “analyser tous les fichiers Python du dossier courant et ses sous-dossiers”. Vous pouvez aussi cibler un fichier spécifique :

Fenêtre de terminal
ruff check mon_script.py

Exemple de sortie :

src/utils.py:12:5: F841 Local variable `result` is assigned but never used
src/api.py:45:1: E302 Expected 2 blank lines, found 1
src/api.py:78:80: E501 Line too long (92 > 88)
Found 3 errors.

Chaque ligne indique :

  • Le fichier et la position (ligne:colonne)
  • Le code de la règle (F841, E302, E501)
  • Une description du problème

Beaucoup de problèmes détectés par Ruff peuvent être corrigés automatiquement :

Fenêtre de terminal
ruff check --fix .

Ruff modifie vos fichiers pour corriger les problèmes qu’il sait résoudre. Par exemple, il supprime les imports inutilisés, ajoute les lignes vides manquantes, et modernise la syntaxe.

La commande ruff format reformate votre code comme le ferait Black :

Fenêtre de terminal
ruff format .

C’est tout. Ruff ajuste les espaces, les sauts de ligne, les guillemets, et rend votre code conforme aux conventions Python. La différence avec Black ? Ruff format est quasi instantané, même sur un gros projet.

Pour voir ce qui serait modifié sans appliquer les changements :

Fenêtre de terminal
ruff format --check .

En pratique, vous lancez souvent les deux :

Fenêtre de terminal
ruff check --fix .
ruff format .

L’ordre est important : corrigez d’abord avec le linter (qui peut ajouter ou supprimer du code), puis formatez (qui ajuste la présentation).

Ruff supporte plus de 800 règles provenant de différents outils. Chaque règle a un code qui indique son origine et son type.

Les règles sont organisées par préfixe. Voici les plus courantes :

PréfixeOrigineCe qu’elles vérifient
EpycodestyleErreurs de style (espaces, indentation)
WpycodestyleAvertissements de style
FPyflakesErreurs logiques (variables non utilisées, imports manquants)
IisortOrganisation des imports
Bflake8-bugbearBugs potentiels et mauvaises pratiques
UPpyupgradeSyntaxe obsolète à moderniser
SIMflake8-simplifyCode qui peut être simplifié
DpydocstyleDocstrings manquantes ou mal formatées
Npep8-namingConventions de nommage
Sflake8-banditProblèmes de sécurité

Par défaut, Ruff active les règles E et F — les erreurs de style et les erreurs logiques de base. C’est un bon point de départ, mais vous voudrez probablement en ajouter.

Pour voir quelles règles sont actives sur votre projet :

Fenêtre de terminal
ruff check --show-settings | grep "select"

Voici quelques règles que je recommande d’activer :

F841 — Variable locale assignée mais jamais utilisée :

# ❌ Ruff signale cette erreur
def calculate():
result = 42 # F841: 'result' est assigné mais jamais utilisé
return 0

E501 — Ligne trop longue (plus de 88 caractères par défaut) :

# ❌ Ligne trop longue
message = "Ceci est un message très très très très très très très très très long"

I001 — Imports mal organisés :

# ❌ Imports dans le désordre
import os
from collections import defaultdict
import sys # devrait être avec os
# ✅ Après correction automatique
import os
import sys
from collections import defaultdict

UP — Syntaxe à moderniser :

# ❌ Syntaxe Python 2/ancien Python 3
"{} {}".format(first, last)
# ✅ Après correction (f-string)
f"{first} {last}"

La configuration de Ruff se fait dans pyproject.toml. C’est là que vous choisissez quelles règles activer et comment Ruff doit se comporter.

Voici une configuration de départ équilibrée :

[tool.ruff]
# Version Python cible (pour les règles de modernisation)
target-version = "py311"
# Longueur de ligne (88 = défaut de Black)
line-length = 88
# Dossiers à ignorer
exclude = [
".git",
".venv",
"__pycache__",
"build",
"dist",
]
[tool.ruff.lint]
# Règles à activer
select = [
"E", # Erreurs de style pycodestyle
"F", # Erreurs Pyflakes (variables non utilisées, etc.)
"I", # Tri des imports (isort)
"B", # Bugs potentiels (flake8-bugbear)
"UP", # Modernisation syntaxe (pyupgrade)
"SIM", # Simplifications possibles
]
# Règles à ignorer
ignore = [
"E501", # Ligne trop longue (géré par le formatter)
]
# Règles que --fix peut corriger automatiquement
fixable = ["ALL"]
[tool.ruff.format]
# Style des guillemets (comme Black)
quote-style = "double"
# Indentation (comme Black)
indent-style = "space"

select définit les règles de base. Si vous mettez select = ["E", "F"], seules ces familles sont actives.

extend-select ajoute des règles en plus de celles par défaut ou déjà sélectionnées. Utile pour ajouter progressivement des règles.

ignore désactive des règles spécifiques. Par exemple, ignore = ["E501"] désactive la vérification de longueur de ligne.

Exemple avec extend-select :

[tool.ruff.lint]
# Garde les règles par défaut (E, F) et ajoute I, B, UP
extend-select = ["I", "B", "UP"]

Parfois, une règle ne s’applique pas à un cas précis. Utilisez # noqa :

# Ignorer une règle sur une ligne
long_variable_name = "valeur" # noqa: E501
# Ignorer plusieurs règles
import unused_module # noqa: F401, E501
# Ignorer toutes les règles sur une ligne (déconseillé)
problematic_line # noqa

Pour ignorer une règle sur tout un fichier, ajoutez en haut :

# ruff: noqa: F401

Vous pouvez avoir des règles différentes selon les dossiers. Par exemple, être plus strict sur le code source que sur les tests avec per-file-ignores :

[tool.ruff.lint]
select = ["E", "F", "I", "B", "UP"]
[tool.ruff.lint.per-file-ignores]
# Dans les tests, autoriser les assertions et les imports *
"tests/*" = ["S101", "F403"]
# Dans les migrations, moins strict
"migrations/*" = ["E501"]

Ruff est plus utile quand il s’exécute automatiquement. Voici comment l’intégrer dans vos outils quotidiens.

L’extension officielle Ruff pour VS Code analyse votre code pendant que vous tapez et peut corriger automatiquement à la sauvegarde.

  1. Installer l’extension

    Recherchez “Ruff” dans les extensions VS Code (éditeur : Astral Software) et installez-la.

  2. Configurer le formatage automatique

    Dans vos paramètres VS Code (.vscode/settings.json) :

    {
    "[python]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "charliermarsh.ruff",
    "editor.codeActionsOnSave": {
    "source.fixAll.ruff": "explicit",
    "source.organizeImports.ruff": "explicit"
    }
    }
    }
  3. Désactiver les autres extensions

    Si vous avez Pylint, Flake8 ou Black installés, désactivez-les pour éviter les conflits et la redondance.

Avec cette configuration, chaque sauvegarde formate votre code et corrige les imports automatiquement.

Pre-commit lance des vérifications avant d’accepter un commit. C’est la dernière ligne de défense avant que du code ne soit versionné.

Créez ou modifiez .pre-commit-config.yaml :

repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.0 # Utilisez la dernière version
hooks:
# Linter avec correction automatique
- id: ruff
args: ["--fix"]
# Formateur
- id: ruff-format

Installez les hooks :

Fenêtre de terminal
pip install pre-commit
pre-commit install

Maintenant, à chaque git commit, Ruff vérifie et formate votre code. Si des fichiers sont modifiés, le commit est annulé — vous devez ajouter les modifications et recommencer.

Ajoutez une vérification Ruff à votre pipeline CI pour garantir que tout le code mergé respecte les règles.

Créez .github/workflows/lint.yml :

name: Lint
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Run Ruff linter
run: uvx ruff check --output-format=github .
- name: Run Ruff formatter
run: uvx ruff format --check .

L’option --output-format=github formate les erreurs pour qu’elles apparaissent directement dans l’interface de pull request, avec des annotations sur les lignes problématiques.

Si votre projet utilise déjà ces outils, voici comment migrer vers Ruff progressivement.

Commencez par ajouter Ruff sans retirer les autres outils :

Fenêtre de terminal
uv add --dev ruff

Lancez Ruff et comparez les résultats avec vos outils actuels :

Fenêtre de terminal
ruff check .

Ruff comprend la plupart des options de Flake8, Black et isort. Voici comment traduire une configuration typique :

Avant (Flake8 + Black + isort) :

.flake8
[flake8]
max-line-length = 88
extend-ignore = E203, E501
pyproject.toml
[tool.black]
line-length = 88
[tool.isort]
profile = "black"

Après (Ruff seul) :

[tool.ruff]
line-length = 88
[tool.ruff.lint]
select = ["E", "F", "I"]
ignore = ["E203", "E501"]
[tool.ruff.lint.isort]
# Compatibilité avec le profil "black"
combine-as-imports = true
force-single-line = false

Une fois que Ruff donne des résultats équivalents, retirez les dépendances :

Fenêtre de terminal
uv remove flake8 black isort

Supprimez les fichiers de configuration devenus inutiles (.flake8, etc.) et mettez à jour votre pre-commit et votre CI.

Si vous activez beaucoup de règles d’un coup sur un projet existant, vous pouvez avoir des centaines d’erreurs. Deux approches :

Corriger progressivement : Activez les règles une par une, corrigez, puis passez à la suivante.

Ignorer l’existant : Utilisez --add-noqa pour ajouter des commentaires # noqa partout où il y a une erreur, puis corrigez au fil du temps :

Fenêtre de terminal
ruff check --add-noqa .

Si vous gardez d’autres outils (mypy, pylint en CI), assurez-vous qu’ils ne vérifient pas les mêmes règles. Désactivez dans Ruff ce que vous préférez laisser à un autre outil.

Parfois, ruff check --fix produit du code que ruff format veut modifier, ce qui crée une boucle. Lancez toujours le linter avant le formatter :

Fenêtre de terminal
ruff check --fix .
ruff format .

Si le problème persiste, c’est probablement une règle de linting incompatible avec le style du formatter. Désactivez-la.

Pour explorer toutes les règles disponibles :

Fenêtre de terminal
ruff rule --all | less

Pour avoir des détails sur une règle spécifique :

Fenêtre de terminal
ruff rule E501
  1. Ruff remplace Flake8, Black, isort, Pylint et pyupgrade en un seul outil
  2. 100-200x plus rapide que les outils traditionnels grâce à Rust
  3. ruff check analyse le code, ruff format le formate
  4. --fix corrige automatiquement beaucoup de problèmes
  5. Configuration dans pyproject.toml avec select et ignore
  6. Intégration VS Code avec correction à la sauvegarde
  7. Pre-commit et GitHub Actions pour automatiser les vérifications