Je suis tombé sur l'instruction Python with
pour la première fois aujourd'hui. J'utilise Python à la légère depuis plusieurs mois et je ne connaissais même pas son existence! Vu son statut quelque peu obscur, j'ai pensé qu'il valait la peine de demander:
with
?try..finally
que with
?Je crois que d'autres utilisateurs avant moi ont déjà répondu à cette question. Je ne l'ajoute donc que par souci d'exhaustivité: l'instruction with
simplifie la gestion des exceptions en encapsulant les tâches de préparation et de nettoyage courantes dans ce qu'on appelle gestionnaires de contexte . Plus de détails peuvent être trouvés dans PEP 34 . Par exemple, l'instruction open
est un gestionnaire de contexte en lui-même, qui vous permet d'ouvrir un fichier, de le garder ouvert tant que l'exécution est dans le contexte de l'instruction with
où vous l'avez utilisé, et de le fermer dès que vous quittez le fichier. contexte, peu importe si vous l’avez quitté à cause d’une exception ou au cours d’un flux de contrôle régulier. L'instruction with
peut donc être utilisée de manière similaire au motif RAII en C++: certaines ressources sont acquises par l'instruction with
et libérées lorsque vous quittez le contexte with
.
En voici quelques exemples: ouverture de fichiers à l'aide de with open(filename) as fp:
, acquisition de verrous à l'aide de with lock:
(où lock
est une instance de threading.Lock
). Vous pouvez également créer vos propres gestionnaires de contexte à l'aide du décorateur contextmanager
à partir de contextlib
. Par exemple, je l'utilise souvent lorsque je dois modifier temporairement le répertoire en cours, puis revenir à l'endroit où j'étais:
from contextlib import contextmanager
import os
@contextmanager
def working_directory(path):
current_dir = os.getcwd()
os.chdir(path)
try:
yield
finally:
os.chdir(current_dir)
with working_directory("data/stuff"):
# do something within data/stuff
# here I am back again in the original working directory
Voici un autre exemple qui redirige temporairement sys.stdin
, sys.stdout
et sys.stderr
vers un autre descripteur de fichier et les restaure ultérieurement:
from contextlib import contextmanager
import sys
@contextmanager
def redirected(**kwds):
stream_names = ["stdin", "stdout", "stderr"]
old_streams = {}
try:
for sname in stream_names:
stream = kwds.get(sname, None)
if stream is not None and stream != getattr(sys, sname):
old_streams[sname] = getattr(sys, sname)
setattr(sys, sname, stream)
yield
finally:
for sname, stream in old_streams.iteritems():
setattr(sys, sname, stream)
with redirected(stdout=open("/tmp/log.txt", "w")):
# these print statements will go to /tmp/log.txt
print "Test entry 1"
print "Test entry 2"
# back to the normal stdout
print "Back to normal stdout again"
Et enfin, un autre exemple qui crée un dossier temporaire et le nettoie en quittant le contexte:
from tempfile import mkdtemp
from shutil import rmtree
@contextmanager
def temporary_dir(*args, **kwds):
name = mkdtemp(*args, **kwds)
try:
yield name
finally:
shutil.rmtree(name)
with temporary_dir() as dirname:
# do whatever you want
Je suggérerais deux conférences intéressantes:
1. L'instruction with
encapsule l'exécution d'un bloc avec des méthodes définies par un gestionnaire de contexte. Cela permet aux modèles d'utilisation try...except...finally
courants d'être encapsulés pour une réutilisation pratique.
2. Vous pouvez faire quelque chose comme:
with open("foo.txt") as foo_file:
data = foo_file.read()
OR
from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
do_something()
OU (Python 3.1)
with open('data') as input_file, open('result', 'w') as output_file:
for line in input_file:
output_file.write(parse(line))
OR
lock = threading.Lock()
with lock:
# Critical section of code
3. Je ne vois pas d’Antipattern ici.
Citer Plongez dans Python :
try..finally is good. avec c'est mieux.
4. J'imagine que c'est lié à l'habitude des programmeurs d'utiliser l'instruction try..catch..finally
provenant d'autres langues.
L'instruction Python with
prend en charge le langage intégré de l'idiome Resource Acquisition Is Initialization
couramment utilisé en C++. Il est conçu pour permettre l'acquisition et la libération sécurisées des ressources du système d'exploitation.
L'instruction with
crée des ressources dans une étendue/un bloc. Vous écrivez votre code en utilisant les ressources du bloc. Lorsque le bloc se ferme, les ressources sont libérées proprement, quel que soit le résultat du code contenu dans le bloc (que le bloc se ferme normalement ou à cause d'une exception).
De nombreuses ressources de la bibliothèque Python qui obéissent au protocole requis par l'instruction with
et peuvent donc être utilisées avec celle-ci immédiatement. Cependant, n'importe qui peut créer des ressources utilisables dans une instruction with en implémentant le protocole bien documenté: PEP 034
Utilisez-le chaque fois que vous acquérez dans votre application des ressources qui doivent être explicitement abandonnées, telles que des fichiers, des connexions réseau, des verrous, etc.
Pour être encore plus complet, j'ajouterai mon cas d'utilisation le plus utile pour les instructions with
.
Je fais beaucoup de calcul scientifique et pour certaines activités, j'ai besoin de la bibliothèque Decimal
pour des calculs de précision arbitraires. Une partie de mon code nécessite une grande précision et, pour la plupart des autres, une précision moindre.
Je règle ma précision par défaut sur un nombre bas, puis utilise with
pour obtenir une réponse plus précise pour certaines sections:
from decimal import localcontext
with localcontext() as ctx:
ctx.prec = 42 # Perform a high precision calculation
s = calculate_something()
s = +s # Round the final result back to the default precision
Je l’utilise beaucoup avec le test hypergéométrique qui nécessite la division de grands nombres résultant en factorielles de forme. Lorsque vous effectuez des calculs à l'échelle génomique, vous devez faire attention aux erreurs d'arrondi et de débordement.
Un exemple d'antipattern pourrait consister à utiliser with
à l'intérieur d'une boucle lorsqu'il serait plus efficace de disposer de with
à l'extérieur de la boucle.
par exemple
for row in lines:
with open("outfile","a") as f:
f.write(row)
contre
with open("outfile","a") as f:
for row in lines:
f.write(row)
La première méthode consiste à ouvrir et à fermer le fichier pour chaque row
, ce qui peut entraîner des problèmes de performances par rapport à la deuxième méthode avec ouvre et ferme le fichier une seule fois.
Voir PEP 343 - L'instruction 'with' , il y a un exemple de section à la fin.
... nouvelle instruction "with" dans le langage Python pour permettre d'extraire les utilisations standard des instructions try/finally.
les points 1, 2 et 3 étant raisonnablement bien couverts:
4: il est relativement nouveau, disponible uniquement en python2.6 + (ou python2.5 en utilisant from __future__ import with_statement
)
L'instruction with fonctionne avec ce qu'on appelle des gestionnaires de contexte:
http://docs.python.org/release/2.5.2/lib/typecontextmanager.html
L'idée est de simplifier la gestion des exceptions en effectuant le nettoyage nécessaire après avoir quitté le bloc 'with'. Certains des éléments intégrés python fonctionnent déjà en tant que gestionnaires de contexte.
Dans python, l'instruction généralement "with" est utilisée pour ouvrir un fichier, traiter les données présentes dans le fichier et également pour fermer le fichier sans appeler de méthode close (). L'instruction "with" simplifie la gestion des exceptions en fournissant des activités de nettoyage.
Forme générale de avec:
with open(“file name”, “mode”) as file-var:
processing statements
note: pas besoin de fermer le fichier en appelant close () sur file-var.close ()
Un autre exemple de prise en charge immédiate, et qui peut paraître un peu déroutant au début, quand vous êtes habitué à la façon dont la fonction intégrée open()
se comporte, sont les objets connection
des modules de base de données courants. tel que:
Les objets connection
sont des gestionnaires de contexte et, en tant que tels, peuvent être utilisés directement dans un with-statement
, toutefois, lorsque vous utilisez la remarque ci-dessus, notez que:
Lorsque le
with-block
est terminé, avec ou sans exception, , la connexion n'est pas fermée . Siwith-block
se termine avec une exception, la transaction est annulée, sinon la transaction est validée.
Cela signifie que le programmeur doit prendre soin de fermer la connexion eux-mêmes, mais permet d’acquérir une connexion et de l’utiliser dans plusieurs with-statements
, comme indiqué dans le psycopg2 docs :
conn = psycopg2.connect(DSN)
with conn:
with conn.cursor() as curs:
curs.execute(SQL1)
with conn:
with conn.cursor() as curs:
curs.execute(SQL2)
conn.close()
Dans l'exemple ci-dessus, vous remarquerez que les objets cursor
de psycopg2
sont également des gestionnaires de contexte. De la documentation pertinente sur le comportement:
Quand un
cursor
quitte lewith-block
il est fermé, libérant ainsi toute ressource éventuellement associée. L'état de la transaction n'est pas affecté.