À quoi est destinée l'instruction python «with»?

419

J'ai rencontré la withdéclaration Python 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! Compte tenu de son statut quelque peu obscur, j'ai pensé qu'il valait la peine de demander:

  1. À quoi sert l' withinstruction Python ?
  2. Qu'utilisez vous pour ça?
  3. Y a-t-il des problèmes que je dois connaître ou des anti-schémas communs associés à son utilisation? Des cas où il vaut mieux l'utiliser try..finallyque with?
  4. Pourquoi n'est-il pas utilisé plus largement?
  5. Quelles classes de bibliothèque standard sont compatibles avec elle?
fmark
la source
5
Juste pour mémoire, voiciwith dans la documentation Python 3.
Alexey
venant d'un arrière-plan Java, cela m'aide à m'en souvenir comme "essai de ressources" correspondant en Java, même si cela n'est peut-être pas tout à fait correct.
vefthym

Réponses:

399
  1. Je pense que cela a déjà été répondu par d'autres utilisateurs avant moi, donc je ne l'ajoute que par souci d'exhaustivité: l' withinstruction simplifie la gestion des exceptions en encapsulant les tâches de préparation et de nettoyage courantes dans les soi-disant gestionnaires de contexte . Plus de détails peuvent être trouvés dans PEP 343 . Par exemple, l' openinstruction est un gestionnaire de contexte en soi, qui vous permet d'ouvrir un fichier, de le garder ouvert tant que l'exécution se trouve dans le contexte de l' withinstruction où vous l'avez utilisée et de la fermer dès que vous quittez le contexte, que vous l'ayez quitté en raison d'une exception ou lors d'un flux de contrôle régulier. L' withinstruction peut donc être utilisée de manière similaire au modèle RAII en C ++: une ressource est acquise par lewithet libérée lorsque vous quittez le withcontexte.

  2. Voici quelques exemples: ouverture de fichiers avec with open(filename) as fp:, acquisition de verrous avec with lock:(où lockest une instance de threading.Lock). Vous pouvez également créer vos propres gestionnaires de contexte à l'aide du contextmanagerdécorateur de contextlib. Par exemple, je l'utilise souvent lorsque je dois changer temporairement le répertoire en cours, puis revenir là 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.stdoutet sys.stderrà une autre poignée de fichiers et restaure eux plus tard:

    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
    
Tamás
la source
20
Merci d'avoir ajouté la comparaison à RAII. En tant que programmeur C ++ qui m'a dit tout ce que j'avais besoin de savoir.
Fred Thomsen
D'accord, permettez-moi de clarifier cela. Vous dites que l' withinstruction est conçue pour remplir une variable avec des données jusqu'à ce que les instructions en dessous soient complètes, puis libérer la variable?
Musixauce3000
Parce que je l'ai utilisé pour ouvrir un script py. with open('myScript.py', 'r') as f: pass. Je pensais pouvoir appeler la variable fpour voir le contenu du texte du document, car c'est ce qui apparaît si le document a été attribué à fpar une régulière opendéclaration: f = open('myScript.py').read(). Mais au lieu que je suis arrivé ce qui suit: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Qu'est-ce que ça veut dire?
Musixauce3000
3
@ Musixauce3000 - l'utilisation withne supprime pas la nécessité readdu fichier réel. Les withappels open- il ne sait pas ce que vous devez en faire - vous voudrez peut-être faire une recherche par exemple.
Tony Suffolk 66
@ Musixauce3000 L' withinstruction peut remplir une variable avec des données ou apporter d'autres modifications à l'environnement jusqu'à ce que les instructions en dessous soient terminées, puis effectue tout type de nettoyage nécessaire. Les types de nettoyage qui peuvent être effectués sont des choses comme la fermeture d'un fichier ouvert, ou comme @Tamas dans cet exemple, le retour des répertoires à l'endroit où vous étiez auparavant, etc. Puisque Python a un ramasse-miettes, la libération d'une variable n'est pas importante cas d'utilisation. withest généralement utilisé pour d'autres types de nettoyage.
Bob Steinke
89

Je suggérerais deux conférences intéressantes:

  • PEP 343 La déclaration "avec"
  • Effbot Comprendre l'instruction "with" de Python

1. L' withinstruction est utilisée pour encapsuler l'exécution d'un bloc avec des méthodes définies par un gestionnaire de contexte. Cela permet try...except...finallyd'encapsuler des modèles d'utilisation courants pour une réutilisation pratique.

2. Vous pourriez faire quelque chose comme:

with open("foo.txt") as foo_file:
    data = foo_file.read()

OU

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))

OU

lock = threading.Lock()
with lock:
    # Critical section of code

3. Je ne vois aucun Antipattern ici.
Citant la plongée en Python :

essayer .. est finalement bon. avec c'est mieux.

4. Je suppose que c'est lié à l'habitude des programmeurs d'utiliser des try..catch..finallyinstructions provenant d'autres langues.

systempuntoout
la source
4
Il prend tout son sens lorsque vous traitez des objets de synchronisation de thread. Relativement rare en Python, mais quand vous en avez besoin, vous en avez vraiment besoin with.
detly
1
diveintopython.org est en panne (définitivement?). Mirrored at diveintopython.net
snuggles
Exemple d'une bonne réponse, ouvrir un fichier est un excellent exemple qui montre dans les coulisses de l'ouverture, io, la fermeture des opérations sur les fichiers sont cachées proprement avec un nom de référence personnalisé
Angry 84
40

L' withinstruction Python est un support de langage intégré de l' Resource Acquisition Is Initializationidiome couramment utilisé en C ++. Il est destiné à permettre l'acquisition et la libération en toute sécurité des ressources du système d'exploitation.

L' withinstruction 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 dans le bloc (c'est-à-dire si le bloc se termine normalement ou en raison d'une exception).

De nombreuses ressources de la bibliothèque Python qui obéissent au protocole requis par l' withinstruction et peuvent donc être utilisées avec elle hors de la boîte. Cependant, n'importe qui peut créer des ressources qui peuvent être utilisées dans une instruction with en implémentant le protocole bien documenté: PEP 0343

Utilisez-le chaque fois que vous acquérez des ressources dans votre application qui doivent être explicitement abandonnées, telles que des fichiers, des connexions réseau, des verrous, etc.

Tendayi Mawushe
la source
27

Encore une fois pour être complet, j'ajouterai mon cas d'utilisation le plus utile pour les withdéclarations.

Je fais beaucoup de calcul scientifique et pour certaines activités, j'ai besoin de la Decimalbibliothèque pour des calculs de précision arbitraires. Certaines parties de mon code ont besoin d'une grande précision et pour la plupart des autres parties, j'ai besoin de moins de précision.

J'ai défini ma précision par défaut sur un nombre faible, puis j'utilise withpour 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

J'utilise beaucoup cela avec le test hypergéométrique qui nécessite la division de grands nombres résultant des factorielles de forme. Lorsque vous effectuez des calculs à l'échelle génomique, vous devez faire attention aux erreurs d'arrondi et de débordement.

JudoWill
la source
26

Un exemple d'antipattern pourrait être d'utiliser l' withintérieur d'une boucle alors qu'il serait plus efficace d'avoir l' withexté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 chacun, rowce qui peut entraîner des problèmes de performances par rapport à la seconde méthode avec ouvre et ferme le fichier une seule fois.

John La Rooy
la source
10

Voir PEP 343 - L'instruction 'with' , il y a un exemple de section à la fin.

... nouvelle instruction "with" au langage Python pour permettre de factoriser les utilisations standard des instructions try / finally.

stefanB
la source
5

les points 1, 2 et 3 étant raisonnablement bien couverts:

4: il est relativement nouveau, uniquement disponible en python2.6 + (ou python2.5 en utilisant from __future__ import with_statement)

cobbal
la source
4

L'instruction with fonctionne avec les soi-disant 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 composants intégrés de python fonctionnent déjà en tant que gestionnaires de contexte.

zefciu
la source
3

Un autre exemple de prise en charge prête à l'emploi, et qui peut être un peu déroutant au début lorsque vous êtes habitué au open()comportement intégré , sont les connectionobjets de modules de base de données populaires tels que:

Les connectionobjets sont des gestionnaires de contexte et en tant que tels peuvent être utilisés prêts with-statementà l'emploi dans un , cependant lors de l'utilisation de la remarque ci-dessus:

Lorsque le with-blockest terminé, avec ou sans exception, la connexion n'est pas fermée . En cas de with-blockfin 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 lui-même, mais permet d'acquérir une connexion et de l'utiliser en plusieurs with-statements, comme indiqué dans les documents psycopg2 :

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 cursorobjets de psycopg2sont également des gestionnaires de contexte. De la documentation pertinente sur le comportement:

Lorsqu'une cursorsortie with-blockest fermée, elle libère toute ressource éventuellement associée. L'état de la transaction n'est pas affecté.

bgse
la source
3

En python, l' instruction « with » est généralement utilisée pour ouvrir un fichier, traiter les données présentes dans le fichier et également pour fermer le fichier sans appeler une 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 ()

Tushar.PUCSD
la source