Définition de l'instruction with en Python
L'instruction with en Python est un mécanisme puissant qui permet de gérer automatiquement les ressources telles que les fichiers, les connexions réseau ou les verrous. Elle garantit qu'une ressource est correctement initialisée avant utilisation et proprement libérée après usage, même si une erreur survient pendant l'exécution du bloc de code.
Concrètement, with repose sur le concept de context manager (gestionnaire de contexte). Lorsque vous utilisez cette instruction, Python appelle automatiquement deux méthodes spéciales : __enter__ à l'entrée du bloc et __exit__ à la sortie, que celle-ci soit normale ou causée par une exception. Ce mécanisme rend votre code plus sûr, plus lisible et plus concis.
Si vous souhaitez maîtriser Python en profondeur, y compris les concepts avancés comme les context managers, nous vous recommandons de suivre notre formation complète sur Python disponible sur Believemy.
Syntaxe de base de with
La syntaxe de l'instruction with est simple et élégante :
with expression as variable:
# bloc de code utilisant la variable
passVoici comment cela fonctionne en détail :
expression: une expression qui retourne un objet supportant le protocole de context manager (c'est-à-dire possédant les méthodes__enter__et__exit__).as variable: (optionnel) assigne la valeur retournée par__enter__à une variable que vous pouvez utiliser dans le bloc.- Le bloc indenté : le code qui s'exécute dans le contexte géré.
À la fin du bloc (ou en cas d'exception), la méthode __exit__ est automatiquement appelée, assurant la libération propre de la ressource.
Pourquoi utiliser with plutôt que try/finally ?
Avant l'introduction de with (Python 2.5), la gestion des ressources passait systématiquement par des blocs try/finally. Comparons les deux approches :
Sans with (approche classique)
fichier = open("donnees.txt", "r")
try:
contenu = fichier.read()
print(contenu)
finally:
fichier.close()Avec with (approche moderne)
with open("donnees.txt", "r") as fichier:
contenu = fichier.read()
print(contenu)La différence est frappante. Avec with, vous n'avez plus besoin de penser à fermer le fichier manuellement. Le fichier est automatiquement fermé à la sortie du bloc, même si une exception est levée. Le code est plus court, plus lisible et moins sujet aux erreurs.
L'instruction with n'est pas limitée aux fichiers. Elle fonctionne avec tout objet implémentant le protocole de context manager, comme les connexions de base de données, les verrous de threads, les transactions, etc.
Exemples pratiques
Lecture et écriture de fichiers
L'utilisation la plus courante de with est la manipulation de fichiers. Voici comment lire et écrire dans des fichiers en toute sécurité :
# Écriture dans un fichier
with open("journal.txt", "w") as fichier:
fichier.write("Première ligne\n")
fichier.write("Deuxième ligne\n")
fichier.write("Troisième ligne\n")
# Lecture du fichier
with open("journal.txt", "r") as fichier:
for ligne in fichier:
print(ligne.strip())Dans cet exemple, même si une erreur se produit pendant l'écriture ou la lecture, le fichier sera correctement fermé grâce à with.
Gestion de plusieurs ressources simultanément
Depuis Python 3.1, vous pouvez gérer plusieurs context managers dans une seule instruction with :
# Copier le contenu d'un fichier dans un autre
with open("source.txt", "r") as source, open("destination.txt", "w") as destination:
for ligne in source:
destination.write(ligne.upper())Cette syntaxe est équivalente à imbriquer deux blocs with, mais elle est beaucoup plus compacte :
# Équivalent avec des blocs imbriqués
with open("source.txt", "r") as source:
with open("destination.txt", "w") as destination:
for ligne in source:
destination.write(ligne.upper())Utilisation avec des verrous (threading)
L'instruction with est particulièrement utile dans la programmation concurrente pour gérer les verrous de manière sûre :
import threading
verrou = threading.Lock()
compteur = 0
def incrementer():
global compteur
with verrou:
# Le verrou est automatiquement acquis ici
valeur_actuelle = compteur
compteur = valeur_actuelle + 1
# Le verrou est automatiquement relâché ici
# Créer et lancer plusieurs threads
threads = [threading.Thread(target=incrementer) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Compteur final : {compteur}") # Affiche : Compteur final : 100Remarquez que dans le cas des verrous, la clause as n'est pas nécessaire car nous n'avons pas besoin d'une variable de retour.
Créer vos propres context managers
Vous pouvez créer vos propres context managers de deux façons : en utilisant une class avec les méthodes spéciales, ou en utilisant le décorateur contextlib.contextmanager.
Méthode 1 : avec une classe
Pour créer un context manager à partir d'une classe, vous devez implémenter les méthodes __enter__ et __exit__ :
class Chronometre:
"""Context manager pour mesurer le temps d'exécution d'un bloc de code."""
def __init__(self, nom="Bloc"):
self.nom = nom
self.debut = None
def __enter__(self):
import time
self.debut = time.time()
print(f"⏱️ Début de la mesure pour '{self.nom}'")
return self # Valeur assignée à la variable après 'as'
def __exit__(self, type_exc, valeur_exc, traceback_exc):
import time
duree = time.time() - self.debut
print(f"⏱️ '{self.nom}' terminé en {duree:.4f} secondes")
return False # Ne pas supprimer les exceptions
# Utilisation
with Chronometre("Calcul intensif") as chrono:
total = sum(range(1_000_000))
print(f"Résultat : {total}")La méthode __exit__ reçoit trois paramètres relatifs à une éventuelle exception :
type_exc: le type de l'exception (ouNonesi aucune exception).valeur_exc: la valeur de l'exception.traceback_exc: le traceback de l'exception.
Si __exit__ retourne True, l'exception est supprimée. Si elle retourne False (ou None), l'exception est propagée normalement.
Méthode 2 : avec contextlib et un générateur
Le module contextlib offre un décorateur qui permet de créer un context manager à partir d'une simple fonction génératrice utilisant yield :
from contextlib import contextmanager
@contextmanager
def repertoire_temporaire(prefixe="tmp"):
import tempfile
import shutil
# Code d'entrée (__enter__)
chemin = tempfile.mkdtemp(prefix=prefixe)
print(f"📁 Répertoire temporaire créé : {chemin}")
try:
yield chemin # Valeur assignée à la variable après 'as'
finally:
# Code de sortie (__exit__)
shutil.rmtree(chemin)
print(f"🗑️ Répertoire temporaire supprimé : {chemin}")
# Utilisation
with repertoire_temporaire("mon_app_") as dossier:
print(f"Travail dans : {dossier}")
# Le dossier existe ici
# Le dossier est automatiquement supprimé iciLorsque vous utilisez @contextmanager, placez toujours le yield dans un bloc try/finally pour garantir que le code de nettoyage s'exécute même en cas d'exception.
Le protocole de context manager en détail
Pour mieux comprendre comment with fonctionne en interne, voici le déroulement précis de l'exécution :
| Étape | Action | Description |
|---|---|---|
| 1 | Évaluation de l'expression | L'expression après with est évaluée pour obtenir le context manager |
| 2 | Appel de __enter__ | La méthode est appelée et sa valeur de retour est assignée à la variable après as |
| 3 | Exécution du bloc | Le code indenté sous with est exécuté |
| 4 | Appel de __exit__ | La méthode est appelée, que le bloc se termine normalement ou par une exception |
| 5 | Gestion d'exception | Si __exit__ retourne True, l'exception est supprimée ; sinon, elle est propagée |
Context managers intégrés courants
Python propose de nombreux context managers intégrés que vous pouvez utiliser directement avec with :
Suppression d'exceptions avec contextlib.suppress
from contextlib import suppress
# Ignorer une erreur spécifique
with suppress(FileNotFoundError):
import os
os.remove("fichier_inexistant.txt")
# Pas d'erreur levée si le fichier n'existe pas
print("Le programme continue normalement")Redirection de la sortie standard
from contextlib import redirect_stdout
import io
# Capturer la sortie de print dans une chaîne
tampon = io.StringIO()
with redirect_stdout(tampon):
print("Ce texte est capturé")
print("Celui-ci aussi")
resultat = tampon.getvalue()
print(f"Texte capturé : {resultat}") # Affiche le texte capturéChangement de répertoire temporaire
import os
from contextlib import contextmanager
@contextmanager
def changer_repertoire(chemin):
ancien = os.getcwd()
os.chdir(chemin)
try:
yield
finally:
os.chdir(ancien)
# Utilisation
print(f"Répertoire actuel : {os.getcwd()}")
with changer_repertoire("/tmp"):
print(f"Répertoire temporaire : {os.getcwd()}")
print(f"Retour au répertoire : {os.getcwd()}")Context managers asynchrones (async with)
Depuis Python 3.5, il est possible d'utiliser with dans un contexte asynchrone grâce à async with. Les méthodes correspondantes sont __aenter__ et __aexit__ :
import asyncio
class SessionHTTP:
"""Exemple simplifié de context manager asynchrone."""
async def __aenter__(self):
print("🌐 Ouverture de la session HTTP")
# Simule l'ouverture d'une connexion
await asyncio.sleep(0.1)
return self
async def __aexit__(self, type_exc, valeur_exc, traceback_exc):
print("🌐 Fermeture de la session HTTP")
# Simule la fermeture de la connexion
await asyncio.sleep(0.1)
return False
async def get(self, url):
print(f"📡 Requête GET vers {url}")
await asyncio.sleep(0.2)
return {"status": 200, "data": "OK"}
async def main():
async with SessionHTTP() as session:
reponse = await session.get("https://api.exemple.com/donnees")
print(f"Réponse : {reponse}")
asyncio.run(main())Bonnes pratiques
Voici les bonnes pratiques à suivre lorsque vous utilisez l'instruction with en Python :
- Préférez toujours
withpour les fichiers : n'utilisez jamaisopen()sanswith. C'est la manière recommandée par la communauté Python et la documentation officielle. - Utilisez
withpour toute ressource nécessitant un nettoyage : connexions de base de données, sockets réseau, verrous, fichiers temporaires, etc. - Gardez les blocs
withcourts : libérez les ressources le plus tôt possible en n'incluant dans le bloc que le code qui a réellement besoin de la ressource. - Préférez
@contextmanagerpour les cas simples : si votre context manager est simple, le décorateur est plus concis qu'une class complète. - N'oubliez pas le
try/finallydans@contextmanager: sans cela, le code de nettoyage ne s'exécutera pas en cas d'exception. - Documentez vos context managers : utilisez des docstring pour expliquer ce que fait votre context manager, ce qu'il retourne et les exceptions qu'il peut gérer.
- Retournez
Falsedans__exit__par défaut : ne supprimez les exceptions que si vous avez une très bonne raison de le faire.
Un bon réflexe : chaque fois que vous écrivez un bloc try/finally pour libérer une ressource, demandez-vous si un context manager ne serait pas plus approprié. La réponse est presque toujours oui.
Questions fréquentes
Quelle est la différence entre with et try/finally en Python ?
Les deux approches permettent de garantir l'exécution d'un code de nettoyage, mais with est plus concis et moins sujet aux erreurs. Avec try/finally, vous devez gérer manuellement l'acquisition et la libération de la ressource, ce qui peut mener à des oublis. L'instruction with encapsule cette logique dans un objet réutilisable (le context manager), rendant le code plus propre et plus maintenable. De plus, le context manager peut être partagé entre plusieurs parties de votre code, favorisant ainsi la réutilisation.
Peut-on utiliser with sans la clause as ?
Oui, la clause as est optionnelle. Elle n'est utile que lorsque vous avez besoin de la valeur retournée par __enter__. Par exemple, avec un verrou (threading.Lock), vous n'avez généralement pas besoin de la variable : with verrou: suffit. En revanche, avec open(), la clause as est indispensable pour accéder à l'objet fichier.
Peut-on imbriquer plusieurs instructions with ?
Oui, vous pouvez imbriquer des blocs with ou utiliser la syntaxe compacte avec des virgules : with open("a.txt") as a, open("b.txt") as b:. Depuis Python 3.10, vous pouvez également utiliser des parenthèses pour une meilleure lisibilité sur plusieurs lignes : with (open("a.txt") as a, open("b.txt") as b):. Les deux approches sont équivalentes fonctionnellement.
Comment apprendre à maîtriser l'instruction with et Python en général ?
L'instruction with fait partie des fonctionnalités essentielles de Python qui distinguent un code amateur d'un code professionnel. Pour maîtriser cette instruction et l'ensemble du langage Python, nous vous recommandons de suivre notre formation Python sur Believemy. Vous y apprendrez non seulement les bases du langage, mais aussi les concepts avancés comme les context managers, la programmation asynchrone et les bonnes pratiques de développement.