Python & AI Tutorials Logo
Programmation Python

28. L’instruction with et les gestionnaires de contexte

Dans le chapitre 27, vous avez déjà utilisé l’instruction with pour travailler avec des fichiers. Elle vous a aidé à lire et à écrire des données sans vous soucier de fermer explicitement le fichier ensuite. À ce moment-là, cependant, l’accent était mis sur comment utiliser with, pas sur ce que cela signifie vraiment.

Dans ce chapitre, nous prenons du recul et regardons la situation dans son ensemble. Vous apprendrez ce que sont les gestionnaires de contexte(context managers), pourquoi gérer des ressources manuellement peut être risqué, et comment l’instruction with fournit un modèle sûr et fiable pour manipuler des ressources en Python. Vous verrez aussi que with ne se limite pas aux fichiers et vous acquerrez une compréhension conceptuelle de son fonctionnement en coulisses.

28.1) Ce que sont les gestionnaires de contexte sur le plan conceptuel

Un gestionnaire de contexte(context manager) est un objet qui définit ce qui doit se passer lorsque vous entrez et sortez d’un contexte particulier dans votre code. Voyez cela comme entrer et sortir d’une pièce : quand vous entrez, vous allumez la lumière ; quand vous sortez, vous l’éteignez — quoi qu’il arrive pendant que vous êtes à l’intérieur.

28.1.1) Le problème de la gestion des ressources

De nombreuses tâches de programmation impliquent d’acquérir une ressource, de l’utiliser, puis de la libérer :

python
# Ouvrir un fichier acquiert une ressource (descripteur de fichier)
file = open("data.txt", "r")
content = file.read()
# Utilisation du fichier...
file.close()  # Libération de la ressource

Ce schéma apparaît fréquemment :

  • Ouvrir et fermer des fichiers
  • Acquérir et libérer des verrous en programmation concurrente
  • Ouvrir et fermer des connexions à une base de données
  • Allouer et désallouer des buffers mémoire

Le défi est de s’assurer que la ressource est toujours libérée, même quand quelque chose se passe mal.

28.1.2) Ce qui fait d’un objet un gestionnaire de contexte

Un gestionnaire de contexte est n’importe quel objet qui implémente deux méthodes spéciales :

  1. __enter__() : appelée lors de l’entrée dans le contexte (au début du bloc with)
  2. __exit__() : appelée lors de la sortie du contexte (à la fin du bloc with, même si une erreur survient)

Vous n’avez pas besoin d’implémenter ces méthodes vous-même pour utiliser des gestionnaires de contexte — les types intégrés de Python comme les objets fichier les possèdent déjà. Comprendre ce concept vous aide à reconnaître quand vous travaillez avec un gestionnaire de contexte.

python
# Les objets fichier sont des gestionnaires de contexte
# Ils ont des méthodes __enter__ et __exit__
file = open("example.txt", "r")
print(hasattr(file, "__enter__"))  # Output: True
print(hasattr(file, "__exit__"))   # Output: True
file.close()

28.1.3) Le schéma de base : initialisation, utilisation, nettoyage

Les gestionnaires de contexte suivent un schéma en trois phases :

Entrée dans le contexte

Initialisation : enter appelée

Utiliser la ressource

Sortie du contexte

Nettoyage : exit appelée

Ressource libérée

Phase d’initialisation : acquérir la ressource (par exemple, ouvrir un fichier, se connecter à une base de données, acquérir un verrou)

Phase d’utilisation : travailler avec la ressource (par exemple, lire/écrire un fichier, interroger une base, accéder à des données partagées)

Phase de nettoyage : libérer la ressource (par exemple, fermer le fichier, se déconnecter de la base, relâcher le verrou)

L’idée clé : la phase de nettoyage a toujours lieu, quoi qu’il arrive pendant la phase d’utilisation.

28.2) Pourquoi la gestion manuelle des ressources est risquée

Avant d’apprendre l’instruction with, comprenons pourquoi la gestion manuelle des ressources peut échouer et causer des problèmes.

28.2.1) L’oubli de fermeture

L’erreur la plus courante consiste simplement à oublier de fermer une ressource :

python
# Lecture d’un fichier de configuration
config_file = open("config.txt", "r")
settings = config_file.read()
# Oups ! Oubli de fermer le fichier
# Le descripteur de fichier reste ouvert

Même si Python finit par fermer les fichiers quand le programme se termine, laisser des fichiers ouverts peut causer des problèmes :

  • Épuisement des ressources : les systèmes d’exploitation limitent le nombre de fichiers ouverts
  • Verrouillage de fichier : d’autres programmes pourraient ne pas pouvoir accéder au fichier
  • Perte de données : les écritures en tampon pourraient ne pas être vidées sur le disque

28.2.2) Les erreurs empêchent le nettoyage

Même quand vous pensez à fermer les ressources, des erreurs peuvent empêcher le code de nettoyage de s’exécuter :

python
# Tentative de traitement d’un fichier
data_file = open("data.txt", "r")
content = data_file.read()
result = process_data(content)  # Et si cela lève une erreur ?
data_file.close()  # Cette ligne ne s’exécute jamais si process_data() échoue !

Si process_data() lève une exception, le programme saute directement à la gestion d’erreur, en ignorant l’appel à close(). Le fichier reste ouvert indéfiniment.

28.2.3) Plusieurs points de sortie

Les fonctions avec plusieurs instructions return rendent le nettoyage encore plus difficile :

python
def read_first_valid_line(filename):
    file = open(filename, "r")
    
    for line in file:
        line = line.strip()
        if line and not line.startswith("#"):
            # Ligne valide trouvée - mais le fichier est toujours ouvert !
            return line
    
    file.close()  # Atteint uniquement si aucune ligne valide n’est trouvée
    return None

La fonction retourne tôt lorsqu’elle trouve une ligne valide, en laissant le fichier ouvert. Il faudrait ajouter file.close() avant chaque return — facile à oublier et difficile à maintenir.

28.2.4) Gestion d’erreurs complexe

Vous pourriez essayer d’utiliser try-except-finally pour garantir le nettoyage :

python
# Tentative de gérer correctement les erreurs
file = None
try:
    file = open("data.txt", "r")
    content = file.read()
    result = process_data(content)
except FileNotFoundError:
    print("File not found")
except ValueError:
    print("Invalid data format")
finally:
    if file is not None:
        file.close()

Cela fonctionne, mais c’est verbeux et sujet aux erreurs. Vous devez :

  • Initialiser la variable avant le bloc try
  • Vérifier si la ressource a été acquise avec succès avant de fermer
  • Penser à inclure le bloc finally
  • Répéter ce schéma pour chaque ressource

28.2.5) L’impact dans le monde réel

Ces problèmes ne sont pas seulement théoriques. Considérez un programme qui traite des milliers de fichiers :

python
# AVERTISSEMENT : fuite de ressources - uniquement à des fins de démonstration
# PROBLÈME : les fichiers ne sont jamais fermés
def process_many_files(filenames):
    results = []
    for filename in filenames:
        file = open(filename, "r")  # Ouvre un fichier
        data = file.read()
        results.append(analyze(data))
        # ERREUR : ne ferme jamais le fichier
    return results
 
# Après avoir traité 1000 fichiers, vous avez 1000 descripteurs de fichiers ouverts !
# Au bout d’un moment, l’OS refuse d’ouvrir davantage de fichiers

Output (after many iterations):

OSError: [Errno 24] Too many open files: 'file_1001.txt'

Le programme plante parce qu’il a épuisé la limite du système pour les descripteurs de fichiers. C’est une fuite de ressources — des ressources sont acquises mais jamais libérées.

28.3) Utiliser with au-delà des fichiers

L’instruction with fonctionne avec n’importe quel gestionnaire de contexte, pas seulement avec les fichiers. Explorons comment elle résout les problèmes que nous avons identifiés et voyons-la utilisée dans divers contextes.

28.3.1) Syntaxe de base de l’instruction with

L’instruction with a une structure simple :

python
with expression as variable:
    # Bloc de code qui utilise la ressource
    # Indenté sous l’instruction with
# Ressource libérée automatiquement ici

L’expression doit s’évaluer en un objet gestionnaire de contexte. La partie as variable est optionnelle mais généralement incluse — elle vous donne un nom pour référencer la ressource.

28.3.2) Utiliser with pour les opérations sur fichiers

Voici comment l’instruction with transforme la gestion des fichiers :

python
# Approche manuelle (risquée)
file = open("data.txt", "r")
content = file.read()
file.close()
 
# Approche avec l’instruction with (sûre)
with open("data.txt", "r") as file:
    content = file.read()
# Fichier fermé automatiquement ici, même si une erreur survient

Le fichier est garanti d’être fermé à la fin du bloc with, que le code se termine normalement ou lève une exception.

28.3.3) Plusieurs gestionnaires de contexte

Vous pouvez gérer plusieurs ressources dans une seule instruction with :

python
# Lire depuis un fichier et écrire dans un autre
with open("input.txt", "r") as input_file, open("output.txt", "w") as output_file:
    for line in input_file:
        processed = line.upper()
        output_file.write(processed)
# Les deux fichiers sont fermés automatiquement ici

Cela équivaut à imbriquer des instructions with, mais c’est plus concis :

python
# Instructions with imbriquées (équivalent mais plus verbeux)
with open("input.txt", "r") as input_file:
    with open("output.txt", "w") as output_file:
        for line in input_file:
            processed = line.upper()
            output_file.write(processed)

Les deux approches garantissent que les deux fichiers sont correctement fermés, même si une erreur survient pendant le traitement.

28.3.4) Travailler avec des fichiers compressés

Le module gzip de Python fournit des gestionnaires de contexte pour lire et écrire des fichiers compressés :

python
import gzip
 
# Écriture de données compressées
with gzip.open("data.txt.gz", "wt") as compressed_file:
    compressed_file.write("This text will be compressed\n")
    compressed_file.write("Saving space on disk\n")
# Fichier fermé automatiquement et compression finalisée
 
# Lecture de données compressées
with gzip.open("data.txt.gz", "rt") as compressed_file:
    content = compressed_file.read()
    print(content)

Output:

This text will be compressed
Saving space on disk

L’instruction with garantit que le fichier compressé est correctement finalisé, ce qui est crucial pour la compression — une compression incomplète peut produire des fichiers corrompus.

28.3.5) Changer de répertoire temporairement

Quand vous devez changer temporairement le répertoire de travail courant, la gestion manuelle peut être risquée :

python
import os
 
# Répertoire courant
print(f"Starting in: {os.getcwd()}")
 
# Changement manuel de répertoire (risqué)
original_dir = os.getcwd()
os.chdir("/tmp")
print(f"Now in: {os.getcwd()}")
process_files()  # Si une erreur survient ici, il se peut que nous ne revenions pas à original_dir
os.chdir(original_dir)

Si process_files() lève une exception, le programme ne revient jamais au répertoire original, ce qui peut provoquer un comportement inattendu dans le code qui suit.

Python 3.11 a introduit contextlib.chdir(), un gestionnaire de contexte qui garantit le retour au répertoire d’origine :

python
import os
from contextlib import chdir
 
print(f"Starting in: {os.getcwd()}")
 
# Utilisation du gestionnaire de contexte (sûr)
with chdir("/tmp"):
    print(f"Temporarily in: {os.getcwd()}")
    process_files()  # Même si cela lève une erreur, on revient au répertoire d’origine
    
print(f"Back in: {os.getcwd()}")
# Retour automatique au répertoire d’origine

Le changement de répertoire est automatiquement annulé lorsque le bloc with se termine, que le code se termine normalement ou lève une exception.

28.3.6) Verrous de thread pour la programmation concurrente

En programmation concurrente (couverte dans des sujets avancés), les verrous sont des gestionnaires de contexte :

python
# Exemple conceptuel (nous apprendrons threading dans les sujets avancés)
import threading
 
lock = threading.Lock()
 
# Gestion manuelle du verrou (risquée)
lock.acquire()
# Section critique - et si une erreur survient ?
lock.release()  # Peut ne pas s’exécuter
 
# Instruction with (sûre)
with lock:
    # Section critique
    # Verrou libéré automatiquement, même si une erreur survient
    pass

28.4) L’instruction with sous le capot (conceptuel uniquement)

Comprendre comment l’instruction with fonctionne en interne vous aide à apprécier sa puissance et à reconnaître quand vous travaillez avec des gestionnaires de contexte. Cette section fournit une vue d’ensemble conceptuelle — vous n’avez pas besoin d’implémenter ces détails vous-même.

28.4.1) Les deux méthodes spéciales

Chaque gestionnaire de contexte implémente deux méthodes spéciales que Python appelle automatiquement :

__enter__(self) : appelée quand le bloc with commence

  • Effectue des opérations d’initialisation (ouverture de fichiers, acquisition de verrous, etc.)
  • Renvoie l’objet ressource qui est affecté à la variable après as
  • Si aucune clause as n’est présente, la valeur de retour est ignorée

__exit__(self, exc_type, exc_value, traceback) : appelée quand le bloc with se termine

  • Effectue des opérations de nettoyage (fermeture de fichiers, libération de verrous, etc.)
  • Reçoit des informations sur toute exception survenue
  • Est toujours appelée, même si une exception a été levée
  • Peut supprimer des exceptions en renvoyant True (rarement utilisé)

28.4.2) Comment Python exécute une instruction with

Traçons ce qui se passe lorsque Python exécute une instruction with :

python
with open("data.txt", "r") as file:
    content = file.read()
    print(content)

Voici l’exécution étape par étape :

Objet fichierInterpréteur PythonVotre codeObjet fichierInterpréteur PythonVotre codeExécuter l’instruction withAppeler __enter__()Renvoyer l’objet fichierAffecter à la variable 'file'Appeler file.read()Renvoyer le contenuAfficher le contenuSortir du bloc withAppeler __exit__()Fermer le fichierRenvoyer NoneContinuer l’exécution

Étape 1 : Python évalue open("data.txt", "r"), en créant un objet fichier

Étape 2 : Python appelle la méthode __enter__() de l’objet fichier

Étape 3 : __enter__() renvoie l’objet fichier lui-même, qui est affecté à file

Étape 4 : Python exécute le bloc de code indenté

Étape 5 : Quand le bloc se termine (normalement ou à cause d’une exception), Python appelle __exit__()

Étape 6 : __exit__() ferme le fichier et effectue le nettoyage

Étape 7 : Si une exception s’est produite, Python la relance après le nettoyage

28.4.3) Gestion des exceptions dans les gestionnaires de contexte

Quand une exception survient à l’intérieur d’un bloc with, Python transmet des informations à __exit__() :

python
# Ce qui se passe quand une erreur survient
try:
    with open("data.txt", "r") as file:
        content = file.read()
        result = int(content)  # Peut lever ValueError
        print(result)
except ValueError as e:
    print(f"Invalid data: {e}")
# Le fichier est fermé avant l’exécution du bloc except

Flux d’exécution quand ValueError se produit :

Entrer dans le bloc with

Appeler enter

Exécuter : content = file.read

Exécuter : result = int content

ValueError levée

Appeler exit avec les infos d’exception

Fermer le fichier

Relancer ValueError

Le bloc except l’intercepte

Le point clé : __exit__() est appelée avant la propagation de l’exception, garantissant que le nettoyage se produit même quand des erreurs surviennent.

28.4.4) Un modèle mental simple

Considérez l’instruction with comme une garantie :

python
with resource_manager as resource:
    # Utiliser la ressource
    pass
# Python GARANTIT que le nettoyage a eu lieu

Quoi qu’il arrive dans le bloc — fin normale, instruction return, exception, ou même erreurs système — Python appelle __exit__() pour nettoyer. Cette garantie est ce qui rend with si puissante et pourquoi vous devriez l’utiliser dès que vous travaillez avec des ressources.


Points clés à retenir de ce chapitre :

  • Les gestionnaires de contexte(context managers) définissent des opérations d’initialisation et de nettoyage pour les ressources
  • La gestion manuelle des ressources est risquée à cause des nettoyages oubliés, des erreurs et de multiples points de sortie
  • L’instruction with garantit que le nettoyage a lieu, même quand des erreurs surviennent
  • Utilisez with pour les fichiers et toute autre ressource nécessitant un nettoyage
  • Plusieurs ressources peuvent être gérées dans une seule instruction with
  • Sous le capot, with appelle automatiquement les méthodes __enter__() et __exit__()
  • __exit__() s’exécute toujours, garantissant que les ressources sont correctement libérées

L’instruction with transforme la gestion des ressources, d’un travail manuel sujet aux erreurs, en un nettoyage automatique et fiable. Utilisez-la dès que vous travaillez avec des fichiers, des connexions à des bases de données, des verrous, ou toute autre ressource nécessitant un nettoyage correct. Votre code sera plus sûr, plus propre et plus professionnel.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai