Aller au contenu

Analyser et Optimiser votre code Python

Mise à jour :

logo python

Maintenir un code Python lisible, performant et sécurisé n’est pas une tâche simple, surtout dans des projets complexes. Pourtant, c’est indispensable. Un bon code n’est pas seulement fonctionnel ; il doit aussi être facile à comprendre, à maintenir et à faire évoluer. Et franchement, qui n’a jamais regretté un code confus quand vient le moment de déboguer ?

La lisibilité, la performance et la sécurité sont les trois piliers d’un code de qualité. Suivre les normes comme la PEP 8, optimiser les parties lentes et protéger votre code contre les vulnérabilités sont autant de défis que les bons outils peuvent vous aider à relever.

Respecter lisibilité, performance et sécurité : un défi

Équilibrer la lisibilité, la performance, et la sécurité d’un projet Python peut vite devenir un vrai casse-tête. Pourquoi ? Parce que ces trois besoins sont souvent en tension. Prenez un exemple simple : vous voulez optimiser un bout de code pour qu’il s’exécute plus vite. Mais en remplaçant une fonction claire par une expression plus rapide mais obscure, vous sacrifiez la lisibilité. À l’inverse, un code hyper lisible mais naïf sur le plan des performances peut ralentir l’ensemble de votre application.

Côté sécurité, les choses se compliquent encore. Souvent, on ne détecte les failles que bien trop tard, une fois le code en production. Et si vous pensez que cela ne vous concerne pas parce que votre application n’est pas publique, détrompez-vous. Même les scripts internes ou les petits projets personnels peuvent être exposés à des risques, comme l’injection de données malveillantes ou des dépendances vulnérables.

Heureusement, il existe des outils pour nous aider. À mon avis, apprendre à respecter ces trois piliers passe par leur utilisation régulière. Ces outils vous permettent de détecter vos erreurs, d’apprendre de vos faiblesses et de progresser en tant que développeur. Que ce soit pour analyser la structure de votre code, vérifier vos dépendances ou optimiser vos performances, ils jouent un rôle clé.

Assurer une lisibilité exemplaire

La lisibilité est souvent sous-estimée, mais elle est essentielle pour un code maintenable. Écrire un code clair, structuré et conforme aux standards permet non seulement de faciliter votre travail, mais aussi de simplifier celui de vos collègues (ou de votre « vous » du futur !). Voici quelques outils indispensables pour améliorer cet aspect.

Black : le formateur automatique

Black est un outil incontournable pour formater votre code Python. Il reformate automatiquement votre code pour qu’il soit conforme aux normes de style, notamment la PEP 8. Son principal atout ? Vous n’avez pas à réfléchir aux espaces, indentations ou conventions de style : Black s’en occupe pour vous.

Exemple d’utilisation :

Terminal window
pip install black
black mon_projet/
reformatted /home/bob/Projets/control-img/linux_image_checker/__init__.py
reformatted /home/bob/Projets/control-img/setup.py
reformatted /home/bob/Projets/control-img/linux_image_checker/inspector.py

En quelques secondes, votre code est propre et uniforme. Adieu les débats sans fin sur les conventions de style !

Pylint : un œil attentif sur vos erreurs

Si vous cherchez un outil pour évaluer la qualité globale de votre code, Pylint est votre allié. Il identifie les erreurs potentielles, les mauvaises pratiques et vous alerte sur les violations des standards.

Exemple d’utilisation :

Terminal window
pip install pylint
pylint setup.py
************* Module setup
setup.py:1:0: C0114: Missing module docstring (missing-module-docstring)
setup.py:9:21: R1732: Consider using 'with' for resource-allocating operations (consider-using-with)
setup.py:9:21: W1514: Using open without explicitly specifying an encoding (unspecified-encoding)

Je corrige les erreurs détectées par Pylint et je m’assure que mon code est conforme aux standards de qualité.

Terminal window
pylint setup.py
--------------------------------------------------------------------
Your code has been rated at 10.00/10 (previous run: 0.00/10, +10.00)

Pylint attribue une note à votre script et fournit des recommandations pour corriger les problèmes détectés. Pratique pour apprendre et progresser.

Ruff : l’outil rapide et polyvalent

Ruff est une solution moderne qui combine les fonctionnalités de plusieurs outils comme Flake8, Pylint, et même Black, le tout en un temps record. Ruff est conçu pour être rapide et léger, idéal pour des projets de toutes tailles.

Exemple d’utilisation :

Terminal window
pipx install ruff
ruff check inspector.py
inspector.py:63:55: F821 Undefined name `os_root`
|
61 | mountpoint = self.g.inspect_get_mountpoints(self.os_info)
62 | if not mountpoint:
63 | logger.error(f"No mount points found for {os_root}")
| ^^^^^^^ F821
64 |
65 | logger.info(f"Mounting {mountpoint} at /")
|
inspector.py:154:5: F841 Local variable `fs_info` is assigned to but never used
|
152 | logger.info("Starting the Linux image inspection")
153 | inspector = LinuxImageInspector(image)
154 | fs_info = inspector.inspect_filesystems()
| ^^^^^^^ F841
155 | logger.info("Filesystem inspection completed")
|
= help: Remove assignment to unused variable `fs_info`
Found 2 errors.
No fixes available (1 hidden fix can be enabled with the `--unsafe-fixes` option).

Dans le code, deux problèmes ont été identifiés :

Variable non définie (os_root) : Une erreur se produisait car le code tentait d’utiliser une variable appelée os_root qui n’existait pas. Cette variable a été remplacée par une information déjà disponible (self.os_info), ce qui corrige l’erreur et rend le message de journalisation fonctionnel.

Variable inutilisée (fs_info) : Une autre erreur signalait que la variable fs_info était assignée mais jamais utilisée. Pour résoudre cela, nous avons supprimé l’affectation inutile et conservé uniquement l’appel de la méthode. Si des détails sur les systèmes de fichiers sont nécessaires, on pourrait les afficher dans les journaux.

Terminal window
ruff check inspector.py
All checks passed!

Ces corrections permettent au code de fonctionner sans erreur tout en restant clair et efficace.

Optimiser les performances du code

Un code performant n’est pas seulement un atout pour vos utilisateurs, c’est aussi un critère essentiel pour garantir l’efficacité de vos projets. En Python, il est facile de tomber dans des pièges de performance, notamment avec des boucles ou des structures de données mal adaptées. Heureusement, des outils comme Radon et Ruff vous aident à identifier ces faiblesses pour les corriger rapidement.

Radon : l’analyse de la complexité cyclomatique

La complexité cyclomatique mesure le nombre de chemins logiques qu’un morceau de code peut emprunter. Plus ce chiffre est élevé, plus le code est difficile à comprendre, à tester et, souvent, moins il est performant. Radon est un outil qui calcule cette complexité pour vous aider à identifier les fonctions ou classes nécessitant une refactorisation.

Exemple d’utilisation :

Terminal window
pip install radon
radon cc mon_projet/ -a
linux_image_checker/inspector.py
M 81:4 LinuxImageInspector.check_image_params - B
C 26:0 LinuxImageInspector - A
M 60:4 LinuxImageInspector.list_users - A
M 34:4 LinuxImageInspector.open_image - A
M 52:4 LinuxImageInspector.inspect_filesystems - A
F 152:0 main - A
M 27:4 LinuxImageInspector.__init__ - A
7 blocks (classes, functions, methods) analyzed.

Expolications des résultats :

  1. Type d’élément : Les lettres M, C, ou F indiquent le type de bloc analysé :

    • M : Méthode (dans une classe).
    • C : Classe.
    • F : Fonction (hors classe).
  2. Position : Les chiffres indiquent la ligne de début du bloc analysé (par exemple, 81:4 signifie que le bloc commence à la ligne 81, avec un niveau d’indentation de 4).

  3. Nom de l’élément : Le nom de la classe, de la méthode ou de la fonction analysée.

  4. Complexité cyclomatique : La lettre (de A à F) indique la complexité cyclomatique.

    • A : Très simple (idéal).
    • B : Modérément complexe (peut nécessiter une attention).
    • C ou plus : Complexe (devrait être simplifié).

Dans cet exemple, la méthode check_image_params est modérément complexe et pourrait être simplifiée. Les autres blocs sont bien conçus et ne nécessitent pas de modification.

En éxaminant check_image_params pour voir si je peux réduire sa complexité. J’ia remarqué que la méthode effectuait plusieurs vérifications similaires. J’ai donc divisé ces vérifications en sous-fonctions pour rendre le code plus lisible et plus facile à maintenir.

Quelques astuces pour optimiser votre code Python

En plus des outils, voici des pratiques simples qui peuvent booster les performances de vos projets Python :

  1. Préférez les compréhensions de liste aux boucles for classiques quand c’est possible :

    # Moins performant
    result = []
    for i in range(10):
    result.append(i * 2)
    # Plus performant
    result = [i * 2 for i in range(10)]
  2. Utilisez les structures de données adaptées. Par exemple, pour des recherches rapides, utilisez un dictionnaire plutôt qu’une liste.

  3. Évitez les opérations inutiles dans les boucles ou les fonctions critiques.

Optimiser les performances demande un peu de pratique, mais les résultats sont gratifiants. Vos scripts seront non seulement plus rapides, mais aussi plus agréables à maintenir et à développer. Avec des outils comme Radon et Ruff pour vous guider, repérer et corriger les points faibles devient un jeu d’enfant.

Les outils pour vérifier la sécurité du code Python

La sécurité est une priorité pour tous les projets, qu’il s’agisse de scripts simples ou d’applications complexes. Des failles peuvent facilement se glisser dans votre code, que ce soit par négligence, manque de vigilance ou ignorance des meilleures pratiques. Heureusement, plusieurs outils permettent de vérifier et de renforcer la sécurité de votre code.

Bandit : détecter les vulnérabilités dans le code source

Bandit est un outil conçu pour analyser le code Python et identifier des problèmes de sécurité courants. Il examine chaque fichier source pour détecter des failles potentielles telles que :

  • Utilisation de fonctions non sécurisées comme eval().
  • Mots de passe ou secrets exposés dans le code.
  • Vulnérabilités liées aux fichiers ou aux permissions.

Exemple d’utilisation :

Terminal window
pip install bandit
[main] INFO profile include tests: None
[main] INFO profile exclude tests: None
[main] INFO cli include tests: None
[main] INFO cli exclude tests: None
[main] INFO running on Python 3.12.3
Run started:2024-12-11 06:39:35.688743
Test results:
No issues identified.
Code scanned:
Total lines of code: 137
Total lines skipped (#nosec): 0
Run metrics:
Total issues (by severity):
Undefined: 0
Low: 0
Medium: 0
High: 0
Total issues (by confidence):
Undefined: 0
Low: 0
Medium: 0
High: 0
Files skipped (0):

Bandit génère un rapport qui détaille :

  • La ligne problématique.
  • Le niveau de gravité (faible, moyen, élevé).
  • Une recommandation pour corriger le problème.

Safety : sécuriser vos dépendances

Vos dépendances Python peuvent représenter un risque important si elles contiennent des failles connues. Safety est un outil qui vérifie les versions des bibliothèques utilisées dans votre projet et les compare à une base de données de vulnérabilités.

Étapes pour l’utiliser :

  1. Générez un fichier requirements.txt si ce n’est pas déjà fait :

    Terminal window
    pip freeze > requirements.txt.test
  2. Lancez Safety pour analyser vos dépendances :

    Terminal window
    pip install safety
    safety scan -r requirements.txt.test
    Safety 3.2.12 scanning /home/bob/Projets/control-img
    2024-12-11 06:43:52 UTC
    Account: Stéphane ROBERT, xxxxxxxxxxxxxxxxxxxxxxx
    Environment: Stage.development
    Scan policy: None, using Safety CLI default policies
    Python detected. Found 1 Python requirement file and 1 Python environment
    requirements.txt: No issues found.
    .direnv/python-3.12/pyvenv.cfg: No issues found.
    Tested 57 dependencies for security issues using default Safety CLI policies
    9 vulnerabilities found, 9 ignored due to policy.
    0 fixes suggested, resolving 0 vulnerabilities.

Safety liste les bibliothèques vulnérables et propose les versions à jour. Cela vous aide à protéger votre application des attaques exploitant des failles connues.

Gestion des secrets dans le code

L’exposition accidentelle de secrets (clés API, mots de passe, etc.) dans le code source est une faille fréquente et critique. Des outils comme TruffleHog et Gitleaks sont spécialement conçus pour détecter ces informations sensibles dans vos dépôts.

Pour en savoir plus sur leur configuration et leur utilisation, je vous invite à consulter les guides dédiés :

Ces outils sont essentiels pour prévenir l’exposition de données sensibles et garantir un code sûr.

pre-commit et pipelines CI/CD

Pour garantir un code Python lisible, performant, et sécurisé, il est essentiel d’automatiser l’utilisation des outils d’analyse. Une intégration bien pensée, à la fois en pre-commit et dans les pipelines CI/CD, permet de détecter et corriger les problèmes dès les premières étapes du développement.

Configurer des hooks pre-commit

Les hooks pre-commit sont une excellente manière d’appliquer des vérifications avant même que le code ne soit poussé dans un dépôt Git. Ils permettent de gagner du temps en interceptant les erreurs ou failles dès le stade local.

Installez l’outil pre-commit dans votre environnement :

Terminal window
pip install pre-commit

Ajoutez un fichier de configuration .pre-commit-config.yaml à la racine de votre projet pour définir les outils que vous souhaitez exécuter.

Par exemple :

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
exclude: ^(env|\.venv|\.direnv|site-packages|node_modules)/
- id: end-of-file-fixer
exclude: ^(env|\.venv|\.direnv|site-packages|node_modules)/
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.2
hooks:
- id: ruff
- repo: https://github.com/PyCQA/bandit
rev: 1.8.0
hooks:
- id: bandit
- repo: https://github.com/gitleaks/gitleaks
rev: v8.17.0
hooks:
- id: gitleaks
args: ["detect", "-v"]

Activez les hooks avec la commande suivante :

Terminal window
pre-commit install
pre-commit installed at .git/hooks/pre-commit
pre-commit autoupdate

Chaque fois que vous faites un commit, ces outils s’exécutent automatiquement. Si un problème est détecté, le commit sera bloqué jusqu’à ce que le code soit corrigé.

Terminal window
git commit -m "Ajout de la fonction de vérification des images"
trim trailing whitespace.................................................Passed
fix end of files.........................................................Passed
ruff.....................................................................Passed
bandit...................................................................Passed
Detect hardcoded secrets.................................................Passed
[master (root-commit) d3100d6] test
12 files changed, 641 insertions(+)
create mode 100644 .gitignore
create mode 100644 .pre-commit-config.yaml
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 config.yaml
create mode 100644 linux_image_checker/__init__.py
create mode 100644 linux_image_checker/inspector.py
create mode 100644 logging_config.yaml
create mode 100644 requirements.txt
create mode 100644 requirements.txt.cc
create mode 100644 requirements.txt.test
create mode 100644 setup.py

Intégration dans un pipeline CI/CD

Automatiser ces outils dans un pipeline CI/CD garantit que les contrôles sont appliqués à chaque modification, indépendamment de l’environnement local des développeurs.

Voici comment intégrer Bandit, Ruff, et Safety dans un fichier .gitlab-ci.yml :

stages:
- lint
- security
lint:
stage: lint
image: python:3.10
script:
- pip install ruff
- ruff check .
only:
- merge_requests
security:
stage: security
image: python:3.10
script:
- pip install bandit safety
- bandit -r .
- safety check -r requirements.txt
only:
- merge_requests

En combinant des hooks pre-commit et des vérifications dans vos pipelines CI/CD, vous établissez une première ligne de défense solide pour votre code Python. Cela permet non seulement de maintenir un code de haute qualité, mais aussi d’automatiser des tâches répétitives et d’éviter les erreurs coûteuses plus tard dans le cycle de développement. Ce processus est un investissement précieux pour des projets stables, performants et sécurisés.

Conclusion

Améliorer la lisibilité, optimiser la performance, et renforcer la sécurité de votre code Python n’est pas une simple formalité : c’est une démarche essentielle pour garantir des projets robustes, évolutifs et fiables. Grâce aux outils modernes comme Ruff, Bandit, Safety, et bien d’autres, ces objectifs ne relèvent plus du casse-tête. Ces solutions vous aident à détecter les erreurs, à prévenir les vulnérabilités, et à automatiser les bonnes pratiques.

Intégrer ces outils dans vos workflows, que ce soit en pre-commit ou dans des pipelines CI/CD, permet de standardiser et d’automatiser les contrôles qualité. Chaque commit, chaque build devient une opportunité de rendre votre code meilleur. L’adoption de ces outils n’est pas seulement un gain de temps pour les développeurs, c’est aussi un investissement dans la fiabilité et la sécurité de vos applications.

En fin de compte, la qualité de votre code n’est pas juste une question de technique, c’est aussi une question de satisfaction personnelle et d’effort collectif. Alors, prenez le temps d’explorer ces outils, de les configurer selon vos besoins, et de les intégrer dans vos processus. Vous verrez rapidement les bénéfices, non seulement sur vos projets, mais aussi sur votre façon de coder.