Retour au cours

backend / python

Modules et packaging : venv, pip, pyproject.toml

Leçon 181 exercice

Explication

Ce que vous allez apprendre

  • Organiser du code en modules (fichier.py) et packages (dossier avec __init__.py)
  • Comprendre à quoi sert if __name__ == "__main__": et pourquoi il est presque partout
  • Créer un environnement virtuel avec venv pour isoler les dépendances d'un projet
  • Installer, figer et rejouer des dépendances avec pip et requirements.txt
  • Décrire un projet avec pyproject.toml, le standard moderne qui remplace setup.py

Dans quel contexte ?

Un développeur travaille sur deux projets sur la même machine : l'un nécessite django==4.2 pour un ancien client, l'autre django==5.0 pour un projet récent. Installer les paquets globalement avec pip install django provoquerait un conflit impossible à résoudre. Créer un environnement virtuel distinct pour chaque projet (python3 -m venv .venv) isole complètement leurs dépendances, comme si chaque projet avait sa propre installation Python.

Passer d'un script à un projet

Un seul fichier .py suffit pour un script rapide, mais un projet réel a besoin d'organiser son code en plusieurs fichiers cohérents. Un module, c'est simplement un fichier .py qu'on peut importer ailleurs. Un package, c'est un dossier de modules regroupés, reconnaissable historiquement par la présence d'un fichier __init__.py.

Pourquoi le test sur name est partout

Un même fichier peut être exécuté directement (python script.py) ou importé depuis un autre fichier (import script). Dans le second cas, on ne veut généralement pas que le code de démonstration du script s'exécute automatiquement. La condition if __name__ == "__main__": distingue les deux situations : __name__ vaut "__main__" seulement lors d'une exécution directe, jamais lors d'un import.

Bonne pratique

Placez toujours le code de démonstration ou de test rapide d'un module sous if __name__ == "__main__":. Un autre développeur qui importera votre module plus tard ne verra jamais ce code s'exécuter par accident.

Isoler les dépendances, projet par projet

Installer des paquets globalement sur sa machine finit toujours par provoquer des conflits de versions entre projets différents. Un environnement virtuel (venv) crée une installation Python isolée et propre à un projet, dans laquelle pip installe les dépendances sans polluer le reste du système. C'est une pratique non négociable dès qu'on travaille sur plus d'un projet Python.

Le standard qui a unifié le packaging

Pendant longtemps, chaque projet Python avait son propre setup.py avec une syntaxe parfois fantaisiste. pyproject.toml est devenu le standard moderne qui décrit un projet (métadonnées, dépendances, outils de build) dans un format déclaratif unique, indépendant de l'outil utilisé pour construire le package.

CommandeEffet
python3 -m venv .venvCrée un environnement virtuel isolé
pip install requestsInstalle un paquet dans l'environnement actif
pip freeze > requirements.txtFige les versions exactes installées
pip install -r requirements.txtRéinstalle exactement les mêmes versions ailleurs

Cette leçon change de registre par rapport aux précédentes : après le langage lui-même, on aborde l'écosystème qui entoure tout projet Python sérieux, thème qui reviendra aux leçons sur les tests et le packaging PyPI.

Commandes & code

Modules et packaging

Organiser du code en modules/packages, et gérer les dépendances proprement.

python
# --- Modules : un fichier .py est un module ---
# fichier : mathutils.py
def carre(x):
    return x ** 2

PI = 3.14159

# fichier : main.py
import mathutils
print(mathutils.carre(4))
print(mathutils.PI)

from mathutils import carre, PI          # import cible
from mathutils import carre as square    # alias
import mathutils as mu                    # alias du module

# --- Packages : un dossier avec __init__.py ---
# structure :
# mon_package/
#   __init__.py
#   core.py
#   utils/
#     __init__.py
#     helpers.py

# mon_package/__init__.py peut exposer une API publique simplifiee :
# from .core import fonction_principale
# from .utils.helpers import aide

# import relatif (uniquement a l'interieur d'un package)
# dans mon_package/utils/helpers.py :
# from ..core import fonction_principale     # remonte d'un niveau

# __name__ == "__main__" : distinguer execution directe vs import
def main():
    print("Programme principal")

if __name__ == "__main__":
    # ce bloc ne s'execute QUE si le fichier est lance directement,
    # pas quand il est importe depuis un autre module
    main()

# __all__ : controle ce que "from module import *" expose
__all__ = ["fonction_publique", "AUTRE_CONSTANTE"]
bash
# --- Environnements virtuels : isoler les dependances par projet ---
python3 -m venv .venv                     # creer un environnement virtuel

# Activation
source .venv/bin/activate                 # Linux / macOS
.venv\Scripts\activate                    # Windows (PowerShell/cmd)

deactivate                                 # quitter l'environnement virtuel

# --- pip : gestionnaire de paquets ---
pip install requests                       # installer un paquet
pip install requests==2.31.0               # version precise
pip install "requests>=2.28,<3.0"          # plage de versions
pip install -e .                           # installation editable du projet courant
pip uninstall requests
pip list                                    # paquets installes
pip freeze > requirements.txt               # figer les versions exactes
pip install -r requirements.txt             # installer depuis un fichier
toml
# --- pyproject.toml : standard moderne (PEP 517/518/621) pour decrire un projet ---
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"

[project]
name = "mon-projet"
version = "1.0.0"
description = "Un projet Python exemplaire"
readme = "README.md"
requires-python = ">=3.11"
license = {text = "MIT"}
authors = [{name = "Alice", email = "alice@example.com"}]
dependencies = [
    "requests>=2.28",
    "pydantic>=2.0",
]

[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff>=0.1", "mypy>=1.5"]

[project.scripts]
mon-cli = "mon_projet.cli:main"      # cree une commande "mon-cli" apres installation

[tool.ruff]
line-length = 100

[tool.pytest.ini_options]
testpaths = ["tests"]
bash
# Outils modernes recommandes en 2024+
pip install uv                             # installateur/resolveur ultra-rapide (remplace pip)
uv venv                                     # cree un venv
uv pip install -r requirements.txt          # installation rapide

# Structure de projet standard recommandee
# mon_projet/
#   pyproject.toml
#   src/
#     mon_projet/
#       __init__.py
#       core.py
#   tests/
#     test_core.py
#   README.md

Résumé

  • Un module est un fichier .py, un package est un dossier avec __init__.py.
  • if __name__ == "__main__" distingue un script exécuté directement d'un module importé.
  • Toujours travailler dans un environnement virtuel (venv) pour isoler les dépendances par projet.
  • pyproject.toml est le standard moderne unifié (remplace setup.py/setup.cfg).

Exercices pratiques

1 disponible
1

Mission : sauver deux projets qui s'entre-détruisent sur la même machine

Objectif : Résoudre un conflit de versions de dépendances entre deux projets avec des environnements virtuels, puis sécuriser un module contre une exécution accidentelle de son code de démonstration.

Contexte

Un développeur travaille sur deux projets sur la même machine : l'un a besoin de django==4.2, l'autre de django==5.0. Il a installé les deux versions globalement avec pip install, et désormais un seul des deux projets fonctionne à la fois, selon la dernière installation effectuée.

Tu dois isoler les deux projets avec des environnements virtuels distincts, puis corriger un module qui exécute son code de démonstration à chaque fois qu'il est importé ailleurs, à cause d'un test __name__ manquant.

Résoudre l’exercice →