Solutions — série 09-1 (pip et environnements virtuels)

Rappel de la règle d'or : on n'ouvre ce fichier qu'après avoir terminé (ou sérieusement séché sur) la série. Compare le raisonnement, pas seulement le code : une solution différente qui marche est souvent valable.

Particularité de cette série : la plupart des « solutions » sont des suites de commandes et leurs sorties, pas des fichiers .py. Tes chemins différeront des exemples — c'est le MOTIF du chemin qui compte (passe-t-il par .venv ?).


Exercice facile a — créer, activer, prouver

Raisonnement — Trois gestes séparés : créer (une fois), activer (par session), vérifier. La « preuve » demandée doit montrer un CHEMIN : seul le chemin de l'interpréteur dit la vérité — l'invite (.venv) peut rester affichée après des manipulations bizarres, le chemin ne ment jamais.

Solution (Git Bash ; l'équivalent PowerShell est en dessous)

mkdir demo-venv
cd demo-venv
python -m venv .venv
source .venv/Scripts/activate
python -c "import sys; print(sys.executable)"

Sortie attendue (l'important : .venv apparaît dans le chemin) :

C:\Users\jtron\...\demo-venv\.venv\Scripts\python.exe

En PowerShell, seule l'activation change :

.venv\Scripts\Activate.ps1

Pourquoi ça marche — L'activation place .venv/Scripts en tête du PATH : la commande python résout maintenant vers l'interpréteur du venv. sys.executable affiche le chemin réel de l'interpréteur en cours — c'est LA commande de vérité, celle qui tranche tous les débats « mais je l'ai installé ! ».

Erreur classique sur cet exercice — Taper source .venv/bin/activate (la version Linux des tutos) et conclure que « le venv est cassé ». Sous Windows, c'est Scripts, pas bin. Autre variante : créer le venv, ne PAS l'activer, et lire un chemin sans .venv — la création ne suffit pas, l'activation est un geste séparé.

Variante plus difficile — Sans activer le venv, exécute directement .venv/Scripts/python.exe -c "import sys; print(sys.executable)". Tu constateras qu'on peut utiliser le python d'un venv SANS l'activer, en l'appelant par son chemin complet — c'est ce que font les outils automatiques (VS Code, les tâches planifiées).


Exercice facile b — pip list dedans / dehors

Raisonnement — Comparer le contenu du venv et celui du Python global. On s'attend à un venv quasi vide (c'est sa raison d'être) et à un global plus ou moins chargé selon l'historique de la machine.

Solution

source .venv/Scripts/activate
pip list
deactivate
pip list

Sortie typique, venv activé :

Package Version
------- -------
pip     25.3

Venv désactivé : la liste du Python global — parfois longue de dizaines de paquets si tu as installé des choses « en vrac » avant de connaître les venvs.

Pourquoi ça marchepip list interroge le site-packages de l'environnement auquel pip appartient. Activer/désactiver change QUEL pip répond, donc quelle liste s'affiche. Un venv neuf ne contient que pip lui-même : l'isolation part de zéro.

Erreur classique sur cet exercice — Conclure « pip est cassé, il ne liste plus mes paquets » quand la liste se vide à l'activation. C'est l'inverse : tout fonctionne, tu regardes simplement dans une boîte neuve. La phrase d'explication attendue : le venv et le Python global ont chacun leur dossier de bibliothèques ; pip list montre celui de l'environnement actif.

Variante plus difficile — Compare pip --version dans les deux états : le chemin affiché après from change aussi. C'est le diagnostic le plus rapide qui existe : une seule commande montre à la fois la version ET l'environnement.


Exercice facile c — installer et vérifier en une commande

Raisonnement — Installer avec la forme blindée, puis vérifier SANS créer de fichier : python -c "..." exécute une mini-ligne de code — outil parfait pour les vérifications rapides d'environnement.

Solution

source .venv/Scripts/activate
python -m pip install requests
python -c "import requests; print(requests.__version__)"

Sortie (version indicative) :

2.33.0

Pourquoi ça marchepython -m pip garantit que le pip utilisé est celui du python actif : les deux ne peuvent pas diverger. L'import qui suit réussit parce qu'il s'exécute dans le MÊME environnement que l'installation. La convention __version__ est suivie par la plupart des grandes bibliothèques.

Erreur classique sur cet exercice — Installer dans le venv, puis vérifier dans un AUTRE terminal (non activé) : ModuleNotFoundError, et la fausse conclusion « l'installation a échoué ». L'activation vaut par terminal, pas par machine.

Variante plus difficile — Trouve où requests est physiquement installé : python -c "import requests; print(requests.__file__)". Le chemin doit traverser .venv/Lib/site-packages/ — visualiser ça une fois ancre définitivement le modèle mental.


Exercice moyen a — geler, détruire, reconstruire

Raisonnement — C'est la répétition générale du scénario « nouvelle machine ». L'ordre est critique : geler AVANT de détruire (un venv supprimé n'est plus interrogeable). La reconstruction doit se faire en deux commandes

Solution

# 1. geler (venv activé)
pip freeze > requirements.txt
cat requirements.txt        # contrôle visuel : requests et ses dépendances

# 2. détruire
deactivate
rm -rf .venv                # PowerShell : Remove-Item -Recurse -Force .venv

# 3. reconstruire
python -m venv .venv
source .venv/Scripts/activate
pip install -r requirements.txt

# 4. vérifier
pip list

Pourquoi ça marcherequirements.txt capture les versions EXACTES (requests==2.33.0), donc la reconstruction est reproductible à l'identique — même comportement, mêmes bugs éventuels, pas de surprise « ça marchait sur mon ancienne machine ». Le venv est jetable précisément parce que ce fichier existe.

Erreur classique sur cet exercice — Faire pip freeze APRÈS deactivate : on photographie alors le Python global (des dizaines de paquets sans rapport avec le projet) et le requirements.txt est pollué. Toujours geler depuis le venv activé, et relire le fichier produit : pour ce projet, il doit tenir en ~5 lignes.

Variante plus difficile — Écris à la main un requirements.txt minimal d'UNE ligne (requests) sans numéro de version, reconstruis, et compare avec la version gelée. Avantage (toujours la dernière version) contre inconvénient (comportement non reproductible dans le temps) : c'est un vrai débat d'ingénierie, les deux styles existent.


Exercice moyen b — l'activation dans les deux shells

Raisonnement — Même venv, deux portes d'entrée. Le but est de rendre les DEUX gestes réflexes, parce que Windows te fera passer d'un shell à l'autre (VS Code ouvre souvent PowerShell par défaut, les tutos sont en bash).

Solution

# Git Bash
source .venv/Scripts/activate
# PowerShell
.venv\Scripts\Activate.ps1

Si PowerShell répond « l'exécution de scripts est désactivée » :

Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

puis relancer l'activation.

Pourquoi ça marche — Les deux scripts (activate sans extension pour bash, Activate.ps1 pour PowerShell) font la même chose — modifier le PATH de la session — mais chaque shell a son langage de script, d'où deux fichiers. RemoteSigned autorise les scripts créés localement (dont celui du venv) tout en exigeant une signature pour ceux téléchargés : c'est le réglage recommandé, pas un contournement sauvage.

Erreur classique sur cet exercice — Dans PowerShell, taper source .venv/Scripts/activate : source n'existe pas en PowerShell (erreur source : le terme n'est pas reconnu...). Inversement, lancer Activate.ps1 depuis Git Bash ne fait rien d'utile. Chaque shell, son script.

Variante plus difficile — Dans chaque shell, après activation, tape deactivate puis vérifie avec python -c "import sys; print(sys.executable)" que tu es bien revenu au Python global. Puis cherche : pourquoi deactivate fonctionne-t-il dans les deux shells sans extension ni chemin ? (Réponse : l'activation a défini une fonction/commande temporaire de ce nom dans ta session.)


Exercice moyen c — provoquer LE piège, écrire la checklist

Raisonnement — On fabrique volontairement la panne la plus fréquente du niveau pour apprendre à la reconnaître en conditions calmes. Le livrable important n'est pas le crash, c'est la checklist de diagnostic qu'on en tire.

Solution

deactivate                  # s'assurer qu'aucun venv n'est actif
echo "import requests" > check.py
python check.py

Sortie (si le Python global n'a pas requests ; sinon remplace par un module que tu n'as jamais installé, p. ex. import nonexistent_lib) :

Traceback (most recent call last):
  File "check.py", line 1, in <module>
    import requests
ModuleNotFoundError: No module named 'requests'

La checklist à noter dans le journal :

ModuleNotFoundError ? Trois commandes, dans l'ordre :
1. python -c "import sys; print(sys.executable)"   → QUI exécute ?
2. pip --version                                   → OÙ pip installe-t-il ?
3. Les deux chemins pointent-ils le même environnement, celui du projet ?
   Non → activer le venv (ou sélectionner l'interpréteur dans VS Code), réessayer.
   Oui → le paquet n'est vraiment pas installé (ou faute de frappe d'import).

Pourquoi ça marche — L'erreur est reproduite dans un cas où TU sais pourquoi elle arrive : le module existe bien… dans le venv, pas dans l'environnement qui exécute. Le jour où elle surviendra par accident, tu reconnaîtras la situation au lieu de réinstaller au hasard.

Erreur classique sur cet exercice — Le réflexe panique pip install requests immédiat, SANS diagnostic : ça « répare » en installant dans le global — exactement le désordre que les venvs veulent éviter, et le problème reviendra au prochain projet. Diagnostiquer d'abord, installer ensuite, au bon endroit.

Variante plus difficile — Reproduis la variante VS Code : venv activé dans le terminal intégré, mais interpréteur global sélectionné pour le bouton ▶. Le même fichier marche au terminal et plante au bouton. Répare via « Python: Select Interpreter » — c'est la panne n°1 des débutants sous VS Code.


Exercice difficile a — env_report.py

Raisonnement — Un rapport d'environnement en pur stdlib. Trois infos directes (sys.executable, sys.version, comparaison prefix/base_prefix) et une plus exotique : lister les paquets SANS pip — c'est le rôle de importlib.metadata, l'API que pip lui-même expose au runtime.

Solution

# env_report.py — diagnostic of the running Python environment (stdlib only)
import sys
import importlib.metadata

print("=" * 50)
print("Interpreter :", sys.executable)
print("Version     :", sys.version.split()[0])

in_venv = sys.prefix != sys.base_prefix
print("Active venv :", "oui" if in_venv else "non")
if in_venv:
    print("Venv path   :", sys.prefix)

print("-" * 50)
packages = sorted(
    (dist.metadata["Name"], dist.version)
    for dist in importlib.metadata.distributions()
)
if packages:
    for name, version in packages:
        print(f"  {name:<30} {version}")
    print(f"{len(packages)} paquet(s) installé(s)")
else:
    print("Aucun paquet tiers installé.")
print("=" * 50)

Sortie type, venv activé avec requests :

==================================================
Interpreter : C:\...\demo-venv\.venv\Scripts\python.exe
Version     : 3.14.3
Active venv : oui
Venv path   : C:\...\demo-venv\.venv
--------------------------------------------------
  certifi                        2025.10.5
  charset_normalizer             3.4.4
  idna                           3.11
  pip                            25.3
  requests                       2.33.0
  urllib3                        2.5.0
6 paquet(s) installé(s)
==================================================

Pourquoi ça marche — Dans un venv, sys.prefix pointe vers le venv tandis que sys.base_prefix pointe vers le Python d'origine : leur inégalité est la définition officielle de « je tourne dans un venv » (c'est le test documenté dans la doc Python). importlib.metadata.distributions() énumère les paquets du site-packages actif — la même source d'information que pip list, sans dépendre de pip.

Erreur classique sur cet exercice — Tenter import pip puis appeler ses fonctions internes pour lister les paquets : fragile (API interne, non garantie) et inutile puisque importlib.metadata est là pour ça. Autre écueil : tester le venv avec « le chemin contient .venv » — ça casse dès qu'un venv s'appelle autrement (env/, venv310/…) ; la comparaison des préfixes est la seule méthode fiable.

Variante plus difficile — Fais accepter à env_report.py un argument --json (via sys.argv) qui sort le même rapport en JSON sur la sortie standard (⏩ json.dumps : leçon 09-3). Tu obtiens un outil de diagnostic scriptable — utilisable par un AUTRE programme, pas seulement par tes yeux.


Mini-projet — « kit de démarrage projet »

Raisonnement — Rien de nouveau : l'enjeu est de dérouler le cycle complet SANS la leçon sous les yeux, et de produire un README que quelqu'un d'autre peut suivre. Si une étape te demande de rouvrir la leçon, c'est l'exercice à refaire demain.

Solution — structure finale attendue :

meteo-projet/
  .venv/               ← existe localement, absent de git
  .gitignore           ← contient la ligne : .venv/
  requirements.txt     ← requests==... et ses dépendances
  README.md

Un README.md qui remplit le contrat :

# meteo-projet

Outil météo en ligne de commande (leçons 09-2 / 09-3).

## Installation

    python -m venv .venv

    # Git Bash :
    source .venv/Scripts/activate
    # PowerShell :
    .venv\Scripts\Activate.ps1

    pip install -r requirements.txt

Vérification : `python -c "import requests; print('ok')"` doit afficher `ok`.

Pourquoi ça marche — Le trio .gitignore + requirements.txt + README est le standard de TOUT projet Python : l'environnement est reproductible (requirements), non versionné (gitignore), et documenté (README). Les leçons 09-2 et 09-3 démarrent directement dans ce dossier.

Erreur classique sur ce projet — Écrire le .gitignore APRÈS un premier git add . : le venv est déjà suivi, et le gitignore ne « détache » pas les fichiers déjà indexés (git rm -r --cached .venv pour réparer). Contrôle final : git status ne doit montrer que 3 fichiers.

Variante plus difficile — Ajoute au README une section « Vérifier son environnement » qui référence ton env_report.py de l'exercice difficile, copié dans le projet. Tu viens d'écrire ton premier outil interne réutilisable.