Per cosa è progettata l'istruzione "with" di Python?


419

Mi sono imbattuto nella withdichiarazione di Python per la prima volta oggi. Uso Python alla leggera da diversi mesi e non sapevo nemmeno della sua esistenza! Dato il suo status un po 'oscuro, ho pensato che varrebbe la pena chiedere:

  1. A cosa serve l' withistruzione Python progettata per essere utilizzata?
  2. Per cosa lo usi?
  3. Ci sono dei gotcha di cui devo essere a conoscenza o dei comuni anti-pattern associati al suo utilizzo? Qualche caso in cui è meglio usarlo try..finallydi with?
  4. Perché non è usato più ampiamente?
  5. Quali classi di libreria standard sono compatibili con essa?

5
Solo per la cronaca, ecco lawith documentazione di Python 3.
Alexey,

proveniente da un background Java, mi aiuta a ricordarlo come il corrispondente "prova con risorse" in Java, anche se ciò potrebbe non essere del tutto corretto.
vefthym,

Risposte:


399
  1. Credo che questo abbia già ricevuto risposta da altri utenti prima di me, quindi lo aggiungo solo per completezza: la withdichiarazione semplifica la gestione delle eccezioni incapsulando le attività comuni di preparazione e pulizia nei cosiddetti gestori di contesto . Maggiori dettagli sono disponibili in PEP 343 . Ad esempio, l' openistruzione è un gestore di contesto in sé, che ti consente di aprire un file, tenerlo aperto fintanto che l'esecuzione è nel contesto dell'istruzione in withcui l'hai utilizzato e chiuderlo non appena lasci il contesto, non importa se l'hai lasciato a causa di un'eccezione o durante il normale flusso di controllo. L' withistruzione può quindi essere utilizzata in modo simile al modello RAII in C ++: alcune risorse vengono acquisite dawithe rilasciato quando lasci il withcontesto.

  2. Alcuni esempi sono: apertura di file mediante with open(filename) as fp:, acquisizione di blocchi mediante with lock:(dove si locktrova un'istanza di threading.Lock). Puoi anche costruire i tuoi gestori di contesto usando il contextmanagerdecoratore di contextlib. Ad esempio, lo uso spesso quando devo modificare temporaneamente la directory corrente e poi tornare al punto in cui mi trovavo:

    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
    

    Ecco un altro esempio che reindirizza temporaneamente sys.stdin, sys.stdouted sys.stderra qualche altro file handle e le ripristina successivi:

    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"
    

    E infine, un altro esempio che crea una cartella temporanea e la pulisce quando lascia il contesto:

    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
    

20
Grazie per aver aggiunto il confronto a RAII. Come programmatore C ++ che mi ha detto tutto quello che dovevo sapere.
Fred Thomsen,

Okay, fammi capire bene. Stai dicendo che l' withistruzione è progettata per riempire una variabile di dati fino a quando le istruzioni sottostanti sono complete e quindi liberare la variabile?
Musixauce3000,

Perché l'ho usato per aprire uno script PY. with open('myScript.py', 'r') as f: pass. Mi aspettavo di essere in grado di chiamare la variabile fper vedere il contenuto del testo del documento, in quanto questo è ciò che apparirebbe se il documento sono stati assegnati al fvia un regolare opendichiarazione: f = open('myScript.py').read(). Ma invece ho ottenuto il seguente: <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Cosa significa?
Musixauce3000,

3
@ Musixauce3000 - l'utilizzo withnon rimuove la necessità readdel file effettivo. Le withchiamate open- non sa cosa devi fare con esso - potresti voler fare una ricerca per esempio.
Tony Suffolk, 66

@ Musixauce3000 L' withistruzione può riempire una variabile di dati o apportare altre modifiche all'ambiente fino a quando le istruzioni in essa contenute non sono complete, quindi esegue qualsiasi tipo di pulizia necessaria. I tipi di pulizia che possono essere fatti sono cose come la chiusura di un file aperto, o come ha fatto @Tamas in questo esempio, riportando le directory a dove eri prima, ecc. Dato che Python ha la garbage collection, liberare una variabile non è importante caso d'uso. withviene generalmente utilizzato per altri tipi di pulizia.
Bob Steinke,

89

Vorrei suggerire due lezioni interessanti:

1. L' withistruzione viene utilizzata per completare l'esecuzione di un blocco con metodi definiti da un gestore di contesto. Ciò consente try...except...finallydi incapsulare schemi di utilizzo comuni per un comodo riutilizzo.

2. Potresti fare qualcosa del tipo:

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

O

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

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

O

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

3. Non vedo alcun Antipattern qui.
Citando Dive in Python :

provare..finalmente va bene. con è meglio.

4. Immagino sia legato all'abitudine dei programmatori di usare try..catch..finallydichiarazioni di altre lingue.


4
Diventa davvero unico quando hai a che fare con il threading degli oggetti di sincronizzazione. Relativamente raro in Python, ma quando ne hai bisogno, ne hai davvero bisogno with.
detenere il

1
diveintopython.org non funziona (permanentemente?). Specchia
coccole l'

Esempio di una buona risposta, aprire il file è un ottimo esempio che mostra dietro le quinte di apertura, io, chiusura delle operazioni del file sono nascoste in modo pulito con un nome di riferimento personalizzato
Angry 84

40

L' withistruzione Python è il supporto del linguaggio integrato del Resource Acquisition Is Initializationlinguaggio comunemente usato in C ++. È destinato a consentire l'acquisizione e il rilascio sicuri delle risorse del sistema operativo.

L' withistruzione crea risorse all'interno di un ambito / blocco. Scrivi il tuo codice usando le risorse all'interno del blocco. Quando il blocco esce, le risorse vengono rilasciate in modo pulito indipendentemente dall'esito del codice nel blocco (ovvero se il blocco esce normalmente o a causa di un'eccezione).

Molte risorse nella libreria Python che obbediscono al protocollo richiesto withdall'istruzione e che quindi possono essere utilizzate immediatamente. Tuttavia, chiunque può creare risorse che possono essere utilizzate in un'istruzione with implementando il protocollo ben documentato: PEP 0343

Usalo ogni volta che acquisisci risorse nella tua applicazione che devono essere esplicitamente abbandonate come file, connessioni di rete, blocchi e simili.


27

Ancora una volta per completezza aggiungerò il mio caso d'uso più utile per le withdichiarazioni.

Faccio molto calcolo scientifico e per alcune attività ho bisogno della Decimalbiblioteca per calcoli arbitrari di precisione. Alcune parti del mio codice necessitano di alta precisione e per la maggior parte delle altre parti ho bisogno di meno precisione.

Ho impostato la mia precisione predefinita su un numero basso e quindi uso withper ottenere una risposta più precisa per alcune sezioni:

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

Lo uso molto con il test ipergeometrico che richiede la divisione di grandi numeri risultanti da fattoriali. Quando si eseguono calcoli su scala genomica, è necessario fare attenzione agli errori di arrotondamento e di overflow.


26

Un esempio di antipattern potrebbe essere quello di utilizzare l' withinterno di un ciclo quando sarebbe più efficiente avere l' withesterno del ciclo

per esempio

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

vs

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

Il primo modo è aprire e chiudere il file per ciascuno di essi rowche può causare problemi di prestazioni rispetto al secondo modo con apre e chiude il file una sola volta.


10

Vedi PEP 343 - L'istruzione 'with' , c'è una sezione di esempio alla fine.

... nuova istruzione "with" nel linguaggio Python per consentire di scomporre gli usi standard delle istruzioni try / finally.


5

i punti 1, 2 e 3 sono ragionevolmente ben coperti:

4: è relativamente nuovo, disponibile solo in python2.6 + (o usando python2.5 usando from __future__ import with_statement)



3

Un altro esempio di supporto immediato e che all'inizio potrebbe essere un po 'sconcertante quando si è abituati al open()comportamento integrato , sono gli connectionoggetti dei moduli di database più diffusi come:

Gli connectionoggetti sono gestori di contesto e come tali possono essere utilizzati immediatamente in a with-statement, tuttavia quando si utilizza la nota sopra che:

Al with-blocktermine, con un'eccezione o senza, la connessione non viene chiusa . Nel caso in cui termini with-blockcon un'eccezione, la transazione viene ripristinata, altrimenti la transazione viene impegnata.

Ciò significa che il programmatore deve aver cura di chiudere la connessione da solo, ma consente di acquisire una connessione e usarla in più with-statements, come mostrato nei documenti 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()

Nell'esempio sopra, noterai che gli cursoroggetti di psycopg2sono anche gestori di contesto. Dalla documentazione pertinente sul comportamento:

Quando uno cursoresce with-block, viene chiuso, rilasciando qualsiasi risorsa eventualmente associata ad esso. Lo stato della transazione non è interessato.


3

In python generalmente l' istruzione “ with ” viene utilizzata per aprire un file, elaborare i dati presenti nel file e anche per chiudere il file senza chiamare un metodo close (). L'istruzione "with" semplifica la gestione delle eccezioni fornendo attività di pulizia.

Forma generale di con:

with open(“file name”, mode”) as file-var:
    processing statements

nota: non è necessario chiudere il file chiamando close () su file-var.close ()

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.