Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Vous avez écrit du code, il marche. Mais est-ce qu’il est propre ? Est-ce qu’un autre développeur pourrait le lire sans grincer des dents ? Et surtout, est-ce que des bugs se cachent dans des coins que vous n’avez pas encore explorés ?

C’est là que l’analyse statique et le formatage entrent en scène. Ces outils vont devenir vos meilleurs alliés : ils scrutent votre code sans même l’exécuter et vous alertent quand quelque chose cloche. Mieux encore, ils peuvent reformater automatiquement votre code pour qu’il soit toujours lisible et cohérent. Ces deux outils permettent de mettre en place un cadre de développement pour vous et vos collaborateurs.

Dans cette partie, on va découvrir les deux outils : ruff (le petit nouveau ultra-rapide écrit en Rust) et pylint (le vétéran toujours aussi pertinent). On va les mettre en situation sur du vrai code — avec de vrais problèmes.

Analyse de code statique

L’utilisation de l’analyse de code statique ainsi que le formatage du code permet entre autre de

  • Définir un cadre de développement.

  • S’assurer que les autres développeurs potentiels comprennent ce cadre facilement.

  • Conserver une uniformité dans le code.

Concernant le formatage de code, on peut citer deux outils

Chacun d’eux va vous permettre de définir la structure visuelle que vous souhaitez que votre code ait tout en essayant de respecter la PEP 8. Celle-ci définit les règles de nommages, les règles d’espaces, les règles d’indentation, ...

Concernant l’analyse statique, celle-ci est très utile car elle permet de voir certains dysfonctionnements avant même d’avoir exécuté du code Python. L’analyse statique peut par exemple vérifier

  • les variables non utilisées,

  • les paramètres de fonctions non utilisés,

  • les comportements étranges

  • ...

Là encore, on peut citer deux outils

pylint a été longtemps le meilleur outil car il était beaucoup plus complet que les autres dans les diagnostics et il est facilement configurable.

ruff est jeune mais il offre une rapidité d’exécution incomparable par rapport aux autres outils. Il est écrit en Rust. Même si ruff voit déjà beaucoup de choses, il n’a pas tous les diagnostics de pylint mais c’est en cours (astral-sh/ruff#970).

Dans la suite, nous allons regarder le comportement de ces deux outils sur des bouts de code afin de voir les différences.

Le mieux pour se faire une idée, c’est de voir ces outils en action sur du vrai code — et du code qui a des problèmes. On va passer en revue plusieurs exemples, du simple non-respect de la PEP 8 jusqu’à des bugs plus subtils que seul un œil attentif (ou un linter) peut repérer.

Exemples

%%file examples/linter/pep8.py
class maCLasse:
    def __init__(a, b = 4):
          self.a = 5

def fonctionADD(a, b,c):
    return a+b
Overwriting examples/linter/pep8.py
! pylint examples/linter/pep8.py
************* Module pep8
examples/linter/pep8.py:3:0: W0311: Bad indentation. Found 10 spaces, expected 8 (bad-indentation)
examples/linter/pep8.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/pep8.py:1:0: C0115: Missing class docstring (missing-class-docstring)
examples/linter/pep8.py:1:0: C0103: Class name "maCLasse" doesn't conform to PascalCase naming style (invalid-name)
examples/linter/pep8.py:2:4: E0213: Method '__init__' should have "self" as first argument (no-self-argument)
examples/linter/pep8.py:3:10: E0602: Undefined variable 'self' (undefined-variable)
examples/linter/pep8.py:2:20: W0613: Unused argument 'b' (unused-argument)
examples/linter/pep8.py:1:0: R0903: Too few public methods (0/2) (too-few-public-methods)
examples/linter/pep8.py:5:0: C0116: Missing function or method docstring (missing-function-docstring)
examples/linter/pep8.py:5:0: C0103: Function name "fonctionADD" doesn't conform to snake_case naming style (invalid-name)
examples/linter/pep8.py:5:21: W0613: Unused argument 'c' (unused-argument)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

! ruff check --select ALL examples/linter/pep8.py
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
examples/linter/pep8.py:1:1: INP001 File `examples/linter/pep8.py` is part of an implicit namespace package. Add an `__init__.py`.
examples/linter/pep8.py:1:1: D100 Missing docstring in public module
examples/linter/pep8.py:1:7: N801 Class name `maCLasse` should use CapWords convention
  |
1 | class maCLasse:
  |       ^^^^^^^^ N801
2 |     def __init__(a, b = 4):
3 |           self.a = 5
  |

examples/linter/pep8.py:1:7: D101 Missing docstring in public class
  |
1 | class maCLasse:
  |       ^^^^^^^^ D101
2 |     def __init__(a, b = 4):
3 |           self.a = 5
  |

examples/linter/pep8.py:2:9: ANN204 Missing return type annotation for special method `__init__`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
  |         ^^^^^^^^ ANN204
3 |           self.a = 5
  |
  = help: Add return type annotation: `None`

examples/linter/pep8.py:2:9: D107 Missing docstring in `__init__`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
  |         ^^^^^^^^ D107
3 |           self.a = 5
  |

examples/linter/pep8.py:2:18: N805 First argument of a method should be named `self`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
  |                  ^ N805
3 |           self.a = 5
  |
  = help: Rename `a` to `self`

examples/linter/pep8.py:2:21: ANN001 Missing type annotation for function argument `b`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
  |                     ^ ANN001
3 |           self.a = 5
  |

examples/linter/pep8.py:2:21: ARG002 Unused method argument: `b`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
  |                     ^ ARG002
3 |           self.a = 5
  |

examples/linter/pep8.py:3:11: F821 Undefined name `self`
  |
1 | class maCLasse:
2 |     def __init__(a, b = 4):
3 |           self.a = 5
  |           ^^^^ F821
4 |
5 | def fonctionADD(a, b,c):
  |

examples/linter/pep8.py:5:5: N802 Function name `fonctionADD` should be lowercase
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |     ^^^^^^^^^^^ N802
6 |     return a+b
  |

examples/linter/pep8.py:5:5: ANN201 Missing return type annotation for public function `fonctionADD`
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |     ^^^^^^^^^^^ ANN201
6 |     return a+b
  |
  = help: Add return type annotation

examples/linter/pep8.py:5:5: D103 Missing docstring in public function
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |     ^^^^^^^^^^^ D103
6 |     return a+b
  |

examples/linter/pep8.py:5:17: ANN001 Missing type annotation for function argument `a`
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |                 ^ ANN001
6 |     return a+b
  |

examples/linter/pep8.py:5:20: ANN001 Missing type annotation for function argument `b`
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |                    ^ ANN001
6 |     return a+b
  |

examples/linter/pep8.py:5:22: ANN001 Missing type annotation for function argument `c`
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |                      ^ ANN001
6 |     return a+b
  |

examples/linter/pep8.py:5:22: ARG001 Unused function argument: `c`
  |
3 |           self.a = 5
4 |
5 | def fonctionADD(a, b,c):
  |                      ^ ARG001
6 |     return a+b
  |

Found 17 errors.
No fixes available (2 hidden fixes can be enabled with the `--unsafe-fixes` option).
%%file examples/linter/missing.py
def process_stuff(params):
    executed = False
    if not params:
        raise ValueError('empty command list')
        for foo in params:
            foo.execute
Overwriting examples/linter/missing.py
! pylint examples/linter/missing.py
************* Module missing
examples/linter/missing.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/missing.py:1:0: C0116: Missing function or method docstring (missing-function-docstring)
examples/linter/missing.py:5:8: W0101: Unreachable code (unreachable)
examples/linter/missing.py:5:12: C0104: Disallowed name "foo" (disallowed-name)
examples/linter/missing.py:6:12: W0104: Statement seems to have no effect (pointless-statement)
examples/linter/missing.py:2:4: W0612: Unused variable 'executed' (unused-variable)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

! ruff check --select ALL examples/linter/missing.py
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
examples/linter/missing.py:1:1: INP001 File `examples/linter/missing.py` is part of an implicit namespace package. Add an `__init__.py`.
examples/linter/missing.py:1:1: D100 Missing docstring in public module
examples/linter/missing.py:1:5: ANN201 Missing return type annotation for public function `process_stuff`
  |
1 | def process_stuff(params):
  |     ^^^^^^^^^^^^^ ANN201
2 |     executed = False
3 |     if not params:
  |
  = help: Add return type annotation: `None`

examples/linter/missing.py:1:5: D103 Missing docstring in public function
  |
1 | def process_stuff(params):
  |     ^^^^^^^^^^^^^ D103
2 |     executed = False
3 |     if not params:
  |

examples/linter/missing.py:1:19: ANN001 Missing type annotation for function argument `params`
  |
1 | def process_stuff(params):
  |                   ^^^^^^ ANN001
2 |     executed = False
3 |     if not params:
  |

examples/linter/missing.py:2:5: F841 Local variable `executed` is assigned to but never used
  |
1 | def process_stuff(params):
2 |     executed = False
  |     ^^^^^^^^ F841
3 |     if not params:
4 |         raise ValueError('empty command list')
  |
  = help: Remove assignment to unused variable `executed`

examples/linter/missing.py:4:15: TRY003 Avoid specifying long messages outside the exception class
  |
2 |     executed = False
3 |     if not params:
4 |         raise ValueError('empty command list')
  |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ TRY003
5 |         for foo in params:
6 |             foo.execute
  |

examples/linter/missing.py:4:26: Q000 [*] Single quotes found but double quotes preferred
  |
2 |     executed = False
3 |     if not params:
4 |         raise ValueError('empty command list')
  |                          ^^^^^^^^^^^^^^^^^^^^ Q000
5 |         for foo in params:
6 |             foo.execute
  |
  = help: Replace single quotes with double quotes

examples/linter/missing.py:4:26: EM101 Exception must not use a string literal, assign to variable first
  |
2 |     executed = False
3 |     if not params:
4 |         raise ValueError('empty command list')
  |                          ^^^^^^^^^^^^^^^^^^^^ EM101
5 |         for foo in params:
6 |             foo.execute
  |
  = help: Assign to variable; remove string literal

examples/linter/missing.py:6:13: B018 Found useless expression. Either assign it to a variable or remove it.
  |
4 |         raise ValueError('empty command list')
5 |         for foo in params:
6 |             foo.execute
  |             ^^^^^^^^^^^ B018
  |

Found 10 errors.
[*] 1 fixable with the `--fix` option (3 hidden fixes can be enabled with the `--unsafe-fixes` option).
%%file examples/linter/numpy_check.py
import numpy as np

a = np.zeros((10, 10), dtype=np.int322)
np.sum(a, axes=0)
Overwriting examples/linter/numpy_check.py
! pylint examples/linter/numpy_check.py
************* Module numpy_check
examples/linter/numpy_check.py:1:0: C0114: Missing module docstring (missing-module-docstring)

-------------------------------------------------------------------
Your code has been rated at 6.67/10 (previous run: 10.00/10, -3.33)

! ruff check --select ALL examples/linter/numpy_check.py
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
examples/linter/numpy_check.py:1:1: INP001 File `examples/linter/numpy_check.py` is part of an implicit namespace package. Add an `__init__.py`.
examples/linter/numpy_check.py:1:1: D100 Missing docstring in public module
Found 2 errors.
%%file examples/linter/similarities/module1.py

def function1(array):
    for i in range(10):
        for j in range(10):
            if (i+j) & 1:
                array[i][j] = 1
            else:
                array[i][j] = 0
Overwriting examples/linter/similarities/module1.py
%%file examples/linter/similarities/module2.py

def function2(array):
    print(array[0])
    for i in range(10):
        for j in range(10):
            if (i+j) & 1:
                array[i][j] = 1
            else:
                array[i][j] = 0
Overwriting examples/linter/similarities/module2.py
! pylint examples/linter/similarities
************* Module similarities.module1
examples/linter/similarities/module1.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/similarities/module1.py:2:0: C0116: Missing function or method docstring (missing-function-docstring)
************* Module similarities.module2
examples/linter/similarities/module2.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/similarities/module2.py:2:0: C0116: Missing function or method docstring (missing-function-docstring)
examples/linter/similarities/module2.py:1:0: R0801: Similar lines in 2 files
==similarities.module1:[2:8]
==similarities.module2:[3:9]
    for i in range(10):
        for j in range(10):
            if (i+j) & 1:
                array[i][j] = 1
            else:
                array[i][j] = 0 (duplicate-code)

------------------------------------------------------------------
Your code has been rated at 6.15/10 (previous run: 6.15/10, +0.00)

! ruff check --select ALL examples/linter/similarities
warning: `incorrect-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `incorrect-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
examples/linter/similarities/__init__.py:1:1: D104 Missing docstring in public package
examples/linter/similarities/module1.py:1:1: D100 Missing docstring in public module
examples/linter/similarities/module1.py:2:5: ANN201 Missing return type annotation for public function `function1`
  |
2 | def function1(array):
  |     ^^^^^^^^^ ANN201
3 |     for i in range(10):
4 |         for j in range(10):
  |
  = help: Add return type annotation: `None`

examples/linter/similarities/module1.py:2:5: D103 Missing docstring in public function
  |
2 | def function1(array):
  |     ^^^^^^^^^ D103
3 |     for i in range(10):
4 |         for j in range(10):
  |

examples/linter/similarities/module1.py:2:15: ANN001 Missing type annotation for function argument `array`
  |
2 | def function1(array):
  |               ^^^^^ ANN001
3 |     for i in range(10):
4 |         for j in range(10):
  |

examples/linter/similarities/module2.py:1:1: D100 Missing docstring in public module
examples/linter/similarities/module2.py:2:5: ANN201 Missing return type annotation for public function `function2`
  |
2 | def function2(array):
  |     ^^^^^^^^^ ANN201
3 |     print(array[0])
4 |     for i in range(10):
  |
  = help: Add return type annotation: `None`

examples/linter/similarities/module2.py:2:5: D103 Missing docstring in public function
  |
2 | def function2(array):
  |     ^^^^^^^^^ D103
3 |     print(array[0])
4 |     for i in range(10):
  |

examples/linter/similarities/module2.py:2:15: ANN001 Missing type annotation for function argument `array`
  |
2 | def function2(array):
  |               ^^^^^ ANN001
3 |     print(array[0])
4 |     for i in range(10):
  |

examples/linter/similarities/module2.py:3:5: T201 `print` found
  |
2 | def function2(array):
3 |     print(array[0])
  |     ^^^^^ T201
4 |     for i in range(10):
5 |         for j in range(10):
  |
  = help: Remove `print`

Found 10 errors.
No fixes available (3 hidden fixes can be enabled with the `--unsafe-fixes` option).

Vous avez vu ruff et pylint à l’œuvre sur les mêmes bouts de code. Vous avez probablement remarqué que ruff est plus rapide et plus verbeux sur certaines choses, tandis que pylint a quelques fonctionnalités en plus dans sa besace. Parlons justement de ces avantages que pylint a gardés pour lui.

Avantages de pylint

%%file examples/linter/numpy_doc.py

def awesome_function(tomatoes, banana):
    """
    This is an awesome function !!

    Parameters
    ----------

    tomatoes:
        red fruit

    apple : int
        number of apple
    """

    print(tomatoes)
    print(banana)

    return tomatoes[0]
Overwriting examples/linter/numpy_doc.py
! pylint --load-plugins=pylint.extensions.docparams examples/linter/numpy_doc.py
************* Module numpy_doc
examples/linter/numpy_doc.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/numpy_doc.py:2:0: W9015: "banana" missing in parameter documentation (missing-param-doc)
examples/linter/numpy_doc.py:2:0: W9016: "banana, tomatoes" missing in parameter type documentation (missing-type-doc)
examples/linter/numpy_doc.py:2:0: W9017: "apple" differing in parameter documentation (differing-param-doc)
examples/linter/numpy_doc.py:2:0: W9018: "apple" differing in parameter type documentation (differing-type-doc)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

%%file examples/linter/spellcheck.py

def some_function():
    """
    This fonction does nothing.

    But it's a example of spell checking
    using pylint.

    Don't look at the grammar and work only
    for doc strings and comments.
    """
    pass
Overwriting examples/linter/spellcheck.py
! export PYENCHANT_LIBRARY_PATH=/opt/homebrew/lib/libenchant-2.dylib; pylint --spelling-dict en_US examples/linter/spellcheck.py
************* Module spellcheck
examples/linter/spellcheck.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/spellcheck.py:4:0: C0402: Wrong spelling of a word 'fonction' in a docstring:
    This fonction does nothing.
         ^^^^^^^^
Did you mean: ''function' or 'fornication' or 'fiction' or 'functions''? (wrong-spelling-in-docstring)
examples/linter/spellcheck.py:12:4: W0107: Unnecessary pass statement (unnecessary-pass)

------------------------------------------------------------------
Your code has been rated at 0.00/10 (previous run: 0.00/10, +0.00)

Pylint a donc de sérieux atouts quand il s’agit de vérifier la qualité des docstrings ou de faire du spell-checking. Mais parfois, il peut être un peu trop bavard. Certaines règles ne vous concernent pas, ou vous voulez simplement les désactiver pour un fichier spécifique. Bonne nouvelle : c’est très simple à faire.

Annuler certaines vérifications

%%file examples/linter/disable.py

# pylint: disable=invalid-name
def function1():
    for a in range(10):
        print(a)
Overwriting examples/linter/disable.py
! pylint examples/linter/disable.py
************* Module disable
examples/linter/disable.py:1:0: C0114: Missing module docstring (missing-module-docstring)
examples/linter/disable.py:3:0: C0116: Missing function or method docstring (missing-function-docstring)

------------------------------------------------------------------
Your code has been rated at 3.33/10 (previous run: 3.33/10, +0.00)

Désactiver des règles à la volée, c’est pratique pour du one-shot. Mais sur un vrai projet, vous allez vouloir une configuration cohérente, partagée par toute l’équipe, qui définit exactement ce qui est vérifié et ce qui ne l’est pas. C’est là qu’intervient le fichier de configuration.

Fichier de configuration de pylint

  • Si vous souhaitez qu’il soit utilisé dès que l’on appelle pylint

! pylint --generate-rcfile > ~/.pylintrc
  • Si vous souhaitez qu’il soit utilisé uniquement pour un module ou un paquet précis

! pylint --generate-rcfile > pylintrc
! pylint ...

Que contient ce fichier ?

  • La possibilité de configurer tout ce qu’on a vu avant.

  • Une gestion plus fine (par exemple les expressions régulières satisfaisant la PEP 8).

  • Le formatage des messages de sortie et le calcul du score.

  • La possibiité d’exécuter pylint en multi-threads.

  • ...

Vous avez maintenant toutes les cartes en main pour configurer pylint exactement comme vous le souhaitez. Mais la théorie, c’est bien ; la pratique, c’est mieux. On va appliquer tout ça au projet splinart.

Le typage statique : mypy et Ty

L’analyse statique avec pylint et ruff check vérifie le style, les bonnes pratiques et certains bugs. Mais il existe une autre catégorie d’outils qui va encore plus loin : les type checkers. Ils exploitent les annotations de type (introduites par la PEP 484) pour détecter des erreurs de logique avant même d’exécuter le code.

mypy

mypy est le type checker historique pour Python, développé par Dropbox puis maintenu par la communauté Python. Il est mature, complet et supporte la quasi-totalité du système de types de Python.

Comme toujours, rien ne vaut un exemple concret :

%%file examples/linter/type_errors.py
from collections.abc import Sequence


def add(a: int, b: int) -> int:
    return a + b


def first_element(items: Sequence[int]) -> int:
    return items[0]


def greet(name: str) -> str:
    return "Hello " + name


# Erreurs de type :
result: int = add(1, "2")          # "2" n'est pas un int
x: str = first_element([1, 2, 3])   # retourne int, assigné à str
length: int = greet("World")        # retourne str, assigné à int

# Variable non typée (mypy l'infère comme Any)
untyped = add(1, 2)  # pas d'erreur car Any est compatible avec tout
! mypy examples/linter/type_errors.py

Mypy détecte les incohérences de types sans avoir à exécuter le code.

Configurer mypy

Comme pylint, mypy se configure via pyproject.toml ou un fichier mypy.ini :

[tool.mypy]
python_version = "3.12"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
check_untyped_defs = true
strict = true

Le mode strict = true active toutes les vérifications optionnelles — c’est le mode recommandé pour un nouveau projet.

Ty — le type checker nouvelle génération

Ty est le nouveau venu dans le monde du typage Python. Créé par Astral (les auteurs de ruff et uv), il est écrit en Rust et se veut 10 à 100 fois plus rapide que mypy et Pyright.

Ses principaux atouts :

  • Ultra-rapide : ty vérifie des projets entiers en une fraction de seconde.

  • Diagnostics riches : messages d’erreur détaillés avec contexte.

  • Conçu pour l’adoption progressive : accepte le code partiellement typé, les redéclarations, etc.

  • Language server intégré : navigation, complétion, code actions, auto-import pour VS Code, PyCharm, Neovim.

  • Fonctionnalités avancées : intersection types, type narrowing sophistiqué, analyse de reachability.

Voyons Ty à l’œuvre sur le même exemple :

! ty check examples/linter/type_errors.py

Configurer Ty

La configuration de Ty se fait dans pyproject.toml, sous la section [tool.ty] :

[tool.ty]
python-version = "3.12"

Ty fonctionne très bien sans configuration. Il détecte automatiquement la version Python et les fichiers à analyser. Pour une configuration plus fine (exclusion de fichiers, règles activées/désactivées), consultez la documentation.

Si vous démarrez un nouveau projet, Ty est un excellent choix. Sur un projet existant avec une configuration mypy déjà rodée, restez sur mypy.

Exercices

Le répertoire step0 reprend la dernière étape du TP précédent.

Références

Ce qu’on a accompli

Vous venez de franchir un cap important. Avoir un code qui marche, c’est une chose ; avoir un code propre, lisible et vérifié, c’en est une autre. Récapitulons :

  1. Formatage automatique : avec ruff format, votre code suit la PEP 8 sans que vous ayez à y penser.

  2. Analyse statique : pylint et ruff check détectent les bugs, le code mort, les mauvaises pratiques, avant même que vous exécutiez quoi que ce soit.

  3. Typage statique : mypy et ty exploitent les annotations de type pour détecter des incohérences logiques avant l’exécution.

  4. Docstrings et spell-check : pylint vérifie que vos docstrings sont cohérentes avec le code, et peut même corriger vos fautes d’orthographe.

  5. Configuration : vous savez personnaliser les règles via un fichier pylintrc ou directement dans pyproject.toml.

Votre code est maintenant plus sûr et plus agréable à lire. Dans la prochaine partie, on va s’attaquer à la documentation avec Sphinx — parce qu’un beau code, c’est bien, mais un code que les autres savent utiliser, c’est mieux !