Fonctions avancées, portée des variables et environnement de développement

Objectifs

À la fin de cette séance, vous serez capables de :

  • Comprendre la portée des variables (scope) et éviter les bugs classiques associés
  • Expliquer le principe de la récursivité (niveau lecture, pas d’écriture attendue à ce stade)
  • Configurer un environnement virtuel isolé avec venv
  • Remplacer des print() de débogage par un système de journalisation avec logging

1. Portée des variables (scope)

La portée d’une variable décrit la zone du programme où elle est accessible.

Variable locale

def calculer():
    resultat = 10 * 2
    return resultat

print(calculer())

resultat existe uniquement dans la fonction.

Variable globale

seuil = 50

def depasse(valeur):
    return valeur > seuil

print(depasse(60))

Les variables globales peuvent être utilisées, mais elles rendent le code moins explicite et plus fragile.

Bonnes pratiques

Il vaut mieux passer les informations nécessaires en arguments plutôt que de dépendre de variables externes.

def depasse(valeur, seuil):
    return valeur > seuil

print(depasse(60, 50))

Cas classique de bug

total_mesures = 0

def ajouter_mesure(valeur):
    total_mesures = total_mesures + valeur

Ce code provoque une erreur car Python considère total_mesures comme une variable locale à la fonction.

La bonne solution consiste à renvoyer une valeur et à la réaffecter à l’extérieur :

def ajouter_mesure(total, valeur):
    return total + valeur

total_mesures = 0
total_mesures = ajouter_mesure(total_mesures, 10)

2. Récursivité — aperçu

Niveau attendu à ce stade : comprendre et expliquer un code récursif simple, pas nécessairement en écrire un en autonomie.

La récursivité consiste à faire appel à la fonction elle-même. Cela permet de résoudre certains problèmes en les décomposant en sous-problèmes plus petits.

Pour qu’une fonction récursive fonctionne correctement, il faut :

  1. un cas de base qui arrête la récursion
  2. un appel récursif avec un problème plus petit
def compte_a_rebours(n):
    if n == 0:
        print("C'est parti !")
    else:
        print(n)
        compte_a_rebours(n - 1)

compte_a_rebours(3)

3. Environnements virtuels avec venv

Pourquoi un environnement dédié ?

Quand on travaille sur plusieurs projets Python, chacun peut avoir besoin de versions différentes des mêmes librairies. Installer toutes les librairies directement sur la machine (globalement) finit tôt ou tard par créer des conflits.

Un environnement virtuel résout ce problème : chaque projet a son propre dossier de dépendances, isolé des autres projets et du système.

Créer et activer un environnement

python -m venv env

Sous Windows :

env\Scripts\activate

Sous macOS / Linux :

source env/bin/activate

Une fois activé, l’invite de commande affiche généralement le nom de l’environnement entre parenthèses, par exemple (env).

Installer des paquets

pip install requests

Le paquet est installé uniquement dans env/, pas globalement sur la machine.

Dépendances du projet

pip freeze > requirements.txt
pip install -r requirements.txt

Désactiver l’environnement

deactivate

Bonnes pratiques

  • Un environnement virtuel par projet.
  • Ne jamais versionner (git) le dossier env/ — seulement requirements.txt.
  • Toujours activer l’environnement avant d’installer des paquets.

4. Journalisation avec logging

Pourquoi pas juste des print() ?

Les print() sont pratiques pour un débogage ponctuel, mais ont des limites : pas de niveaux de gravité, pas de formatage automatique de la date/heure, et il faut les supprimer un par un avant de livrer le code.

Le module logging répond à ces limites.

Niveaux de gravité

  • DEBUG : informations détaillées pour le débogage
  • INFO : confirmation que tout fonctionne
  • WARNING : quelque chose d’inattendu
  • ERROR : erreur sérieuse
  • CRITICAL : erreur très grave

Exemple

import logging

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(levelname)s - %(message)s',
    filename='app.log'
)

logger = logging.getLogger(__name__)

logger.debug("Message de debug")
logger.info("Opération réussie")
logger.warning("Attention !")
logger.error("Erreur détectée")

Exemple avec gestion d’erreur

def traiter_donnees(donnees):
    logger.debug(f"Début du traitement avec {len(donnees)} éléments")
    try:
        resultat = sum(donnees) / len(donnees)
        logger.info(f"Traitement réussi. Moyenne : {resultat}")
        return resultat
    except ZeroDivisionError:
        logger.error("Données vides, impossible de calculer")
        return None

5. Pour aller plus loin (optionnel)

Cette partie n’est pas couverte en classe — à explorer en autonomie si vous finissez les exercices en avance, ou après la séance.

Poetry

Poetry combine gestion des dépendances, environnement virtuel et fichier de verrouillage (poetry.lock) pour garantir que chacun installe exactement les mêmes versions. Documentation : python-poetry.org

pre-commit

pre-commit exécute des vérifications automatiques (formatage, style, erreurs de syntaxe) avant chaque git commit, et bloque le commit si un problème est détecté.

pip install pre-commit
pre-commit install

Intégration continue (CI)

La CI teste automatiquement votre code à chaque push, avant toute fusion. Des outils comme GitHub Actions exécutent les mêmes vérifications que pre-commit, mais sur un serveur, pour tout le monde.

TD

Le TD de la séance est disponible ici.


This site uses Just the Docs, a documentation theme for Jekyll.