2014-10-28 17 views
6

Ho letto, quel file aperto come questo viene chiuso automaticamente al momento di lasciare il con il blocco:con e chiusura di file in Python

with open("x.txt") as f: 
    data = f.read() 
    do something with data 

ancora quando si apre via web, ho bisogno di questo:

from contextlib import closing 
from urllib.request import urlopen 

with closing(urlopen('http://www.python.org')) as page: 
    for line in page: 
     print(line) 

perché e qual è la differenza? (Sto usando Python3)

+0

Stavo per suggerire che qualcuno dovrebbe archiviare un bug di documenti su 'contextlib' per questo, e che se non sei tu chiunque dovrebbe accreditarti ... ma prima che potessi finire, Martijn ha già archiviato il bug, con il link qua dietro. :) – abarnert

risposta

6

I dettagli diventano un po 'tecnico, quindi cominciamo con la versione semplice:

Alcuni tipi sanno essere utilizzato in un comunicato with. Gli oggetti file, come quello che si ottiene da open, sono un esempio di questo tipo. Come risulta, gli oggetti che torni da urllib.request.urlopen, sono anche un esempio di tale tipo, quindi il tuo secondo esempio potrebbe essere scritto allo stesso modo del primo.

Ma alcuni tipi non sanno come essere utilizzati in un'istruzione with.La funzione closing è progettata per racchiudere questi tipi, purché abbiano un metodo close, chiamerà il loro metodo close quando si esce dall'istruzione with.

Naturalmente alcuni tipi non sanno come essere utilizzato in una dichiarazione with, e, inoltre, non può essere utilizzato con closing perché il loro metodo di pulitura non è nominato close (o perché li pulizia è più complicata di un semplice chiudendoli). In tal caso, è necessario scrivere un gestore di contesto personalizzato. Ma anche questo non è solitamente così difficile.


In termini tecnici:

Una dichiarazione with richiede un context manager, un oggetto con __enter__ e __exit__ metodi. Chiamerà il metodo __enter__ e fornirà il valore restituito da tale metodo nella clausola as e chiamerà quindi il metodo __exit__ alla fine dell'istruzione with.

oggetti File ereditano da io.IOBase, che è un manager contesto cui __enter__ metodo stesso ritorna, e la cui __exit__ chiamate self.close().

L'oggetto restituito dal urlopen è (assumendo un URL http o https) un HTTPResponse, che, come dicono i documenti, "può essere utilizzato con un'istruzione with".

La funzione closing:

Return un manager contesto che chiude cosa al termine del blocco. Questo è fondamentalmente equivalente a:

@contextmanager 
def closing(thing): 
    try: 
     yield thing 
    finally: 
     thing.close() 

Non è sempre chiaro al 100% nella documentazione, che tipi sono responsabili di contesto e quali tipi non sono. Soprattutto dal momento che c'è stata una grande spinta da 3.1 a trasformare tutto ciò che potrebbe essere un gestore di contesto in uno (e, se è per questo, a rendere tutto ciò che è per lo più simile a un file in un vero IOBase se ha senso), ma non è ancora Completato al 100% a partire dal 3.4.

Si può sempre provare e vedere. Se ottieni uno AttributeError: __exit__, l'oggetto non è utilizzabile come gestore di contesto. Se pensi che dovrebbe essere, presenta un bug che suggerisce la modifica. Se non ricevi questo errore, ma i documenti non menzionano che è legale, presenta un bug che suggerisce l'aggiornamento dei documenti.

+0

grazie per la spiegazione dettagliata! – nekomimi

7

Non è così. urlopen('http://www.python.org') restituisce un gestore di contesto troppo:

with urlopen('http://www.python.org') as page: 

Questo è documentato sul urllib.request.urlopen() page:

Per FTP, file e URL dati e le richieste esplicitamente trattati da eredità URLopener e FancyURLopener classi, questa funzione restituisce un urllib.response.addinfourl oggetto che può funzionare come gestore contesto [...].

Enfasi mia. Per le risposte HTTP, http.client.HTTPResponse() object viene restituito, che è anche un gestore di contesto:

La risposta è un oggetto iterabile e può essere utilizzato in una dichiarazione con.

Il Examples section inoltre usa l'oggetto come contesto gestore:

Come il sito python.org utilizza utf-8 codifica come specificato è metatag, si utilizzerà lo stesso per decodificare i byte oggetto.

>>> with urllib.request.urlopen('http://www.python.org/') as f: 
...  print(f.read(100).decode('utf-8')) 
... 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtm 

oggetti restituiti da open() sono context managers troppo; implementano i metodi speciali object.__enter__() e object.__exit__().

contextlib.closing() documentation utilizza un esempio con urlopen() non aggiornato; in Python 2 il predecessore per urllib.request.urlopen() non produceva un gestore di contesto e si doveva usare quello strumento per chiudere automaticamente la connessione con un gestore di contesto. Questo problema è stato risolto con i problemi 5418 e 12365, ma quell'esempio non è stato aggiornato. Ho creato issue 22755 chiedendo un esempio diverso.

+0

Stavo proprio per scrivere questo :-). la chiave è che i documenti dicono che restituisce un oggetto "simile a un file". Se non può essere utilizzato come gestore del contesto, non è in realtà simile a un file. – mgilson

+0

ma perché questo esempio è nei documenti python, quindi? https://docs.python.org/3/library/contextlib.html – nekomimi

+1

@nekomimi: probabilmente un holdover da Python 2, in cui l'oggetto non era un gestore di contesto. –