È sicuro rendimento all'interno di un “con” blocco in Python (e perché)?
Domanda
La combinazione di coroutines e acquisizione delle risorse sembra come avrebbe potuto avere qualche involontaria (o poco intuitivo) conseguenze.
La domanda di fondo è se non qualcosa di come funziona:
def coroutine():
with open(path, 'r') as fh:
for line in fh:
yield line
Il che è vero.(È possibile eseguire il test!)
La più profonda preoccupazione è che with
dovrebbe essere qualcosa di alternativo per finally
, in cui si assicura che una risorsa è rilasciato alla fine del blocco.Coroutines possibile sospendere e riprendere l'esecuzione da all'interno il with
blocco, così come è il conflitto viene risolto?
Per esempio, se si apre un file in lettura/scrittura sia all'interno che al di fuori di una coroutine mentre la coroutine non ha ancora restituito:
def coroutine():
with open('test.txt', 'rw+') as fh:
for line in fh:
yield line
a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
for line in fh:
print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?
Aggiornamento
Stavo per scrivere-file bloccato gestire un conflitto nell'esempio precedente, ma poiché la maggior parte dei Sistemi operativi allocare filehandles per processo ci sarà nessuna contesa c'.(Complimenti a @Miglia per indicare l'esempio non ha molto senso.) Ecco il mio rivisto esempio, che mostra una reale condizione di deadlock:
import threading
lock = threading.Lock()
def coroutine():
with lock:
yield 'spam'
yield 'eggs'
generator = coroutine()
assert generator.next()
with lock: # Deadlock!
print 'Outside the coroutine got the lock'
assert generator.next()
Soluzione
Io non capisco che cosa il conflitto si sta chiedendo, né il problema con l'esempio:. E 'bene avere due coesistenti, maniglie indipendenti per lo stesso file
Una cosa che non sapeva che ho imparato in risposta alla tua domanda è che ci sia un nuovo metodo close () su generatori:
close()
solleva una nuova eccezioneGeneratorExit
all'interno del generatore di interrompere l'iterazione. Alla ricezione di questa eccezione, il codice del generatore deve o aumentareGeneratorExit
oStopIteration
.
close()
viene chiamato quando un generatore è garbage collection, quindi questo significa il codice del generatore ottiene un'ultima possibilità di eseguire prima che il generatore viene distrutto. Questa ultima possibilità significa che le dichiarazionitry...finally
in generatori possono essere garantiti per funzionare; la clausolafinally
ora sempre avere la possibilità di correre. Questo mi sembra un po 'minore del linguaggio curiosità, ma che utilizzano generatori etry...finally
in realtà è necessaria al fine di attuare la dichiarazionewith
descritto da PEP 343.http://docs.python.org/ whatsnew / 2.5.html # pep-342-nuovo-generatore-caratteristiche
In modo che gestisce la situazione in cui una dichiarazione with
viene utilizzato in un generatore, ma cede nel mezzo, ma mai restituisce-il metodo __exit__
del manager contesto verrà chiamato quando il generatore è garbage collection.
Modifica :
Per quanto riguarda la questione handle di file: a volte mi dimentico che esistono piattaforme che non sono POSIX-like. :)
Per quanto riguarda le serrature andare, credo che Rafał Dowgird colpisce la testa al chiodo quando dice "Devi solo essere consapevoli del fatto che il generatore è proprio come qualsiasi altro oggetto che contiene le risorse." Non credo che la dichiarazione with
è davvero così rilevante qui, dal momento che questa funzione soffre degli stessi problemi di stallo:
def coroutine():
lock.acquire()
yield 'spam'
yield 'eggs'
lock.release()
generator = coroutine()
generator.next()
lock.acquire() # whoops!
Altri suggerimenti
Non credo che ci sia un vero e proprio conflitto. Devi solo essere consapevoli del fatto che il generatore è proprio come qualsiasi altro oggetto che contiene le risorse, quindi è responsabilità del creatore per assicurarsi che sia correttamente finalizzato (e di evitare conflitti / stallo con le risorse possedute dall'oggetto). L'unico problema (minore) che vedo qui è che i generatori non implementano il protocollo di gestione del contesto (almeno a partire da Python 2.5), in modo da poter non solo:
with coroutine() as cr:
doSomething(cr)
ma invece hanno a:
cr = coroutine()
try:
doSomething(cr)
finally:
cr.close()
Il garbage collector fa il close()
in ogni caso, ma è cattiva pratica a fare affidamento su questo per liberare le risorse.
A causa yield
può eseguire codice arbitrario, sarei molto prudente di possesso di un blocco su una dichiarazione resa. È possibile ottenere un effetto simile in molti altri modi, però, tra cui chiamare un metodo o funzioni che potrebbero essere sia stato uno scostamento o altre modifiche.
Generatori, tuttavia, sono sempre (quasi sempre) "chiusa", sia con una chiamata close()
esplicita, o semplicemente essere garbage collection. La chiusura di un generatore genera un'eccezione GeneratorExit
all'interno del generatore e quindi corre infine clausole, con la dichiarazione di pulizia, ecc Si può intercettare l'eccezione, ma è necessario lanciare o uscire dalla funzione (cioè un'eccezione StopIteration
), piuttosto che la resa. E 'pratica probabilmente scarsa a fare affidamento sulla garbage collector per chiudere il generatore in casi come hai scritto, perché questo potrebbe accadere al più tardi si potrebbe desiderare, e se qualcuno chiama sys._exit (), allora il vostro pulitura potrebbe non accadere a tutti .
Questo sarebbe come mi aspettavo cose su cui lavorare. Sì, il blocco non rilascerà le sue risorse fino al completamento, quindi in questo senso la risorsa è sfuggito è nidificazione lessicale. Tuttavia, ma questo non è diverso a fare una chiamata di funzione che ha cercato di utilizzare la stessa risorsa all'interno di un con blocco - nulla vi aiuta nel caso in cui il blocco ha non ancora terminato, per qualsiasi ragione. Non è davvero niente di specifico per i generatori.
Una cosa che potrebbe valere la pena di preoccuparsi se è il comportamento se il generatore è non ripreso. Mi sarei aspettato il blocco with
di agire come un blocco finally
e chiamare la parte __exit__
al termine, ma questo non sembra essere il caso.
Per un TLDR, guardate in questo modo:
with Context():
yield 1
pass # explicitly do nothing *after* yield
# exit context after explicitly doing nothing
Il Context
termina dopo pass
è fatto (cioènulla), pass
si esegue dopo yield
è fatto (cioèl'esecuzione riprende).Così, il with
finisce dopo viene ripreso il controllo a yield
.
TLDR:Un with
il contesto resta in attesa quando yield
rilascia controllo.
In realtà ci sono solo due regole che sono rilevanti qui:
Quando non
with
rilasciare le sue risorse?Lo fa una volta e direttamente dopo il blocco è fatto.L'ex significa che non rilascia durante un
yield
, come che potrebbe accadere più volte.Il più tardi significa che il rilascio dopoyield
è stata completata.Quando non
yield
completo?Pensate
yield
come un rovescio di chiamata:il controllo viene passato al chiamante, non verso il basso per una chiamata uno.Allo stesso modo,yield
completa quando il controllo viene passato al, proprio come quando una chiamata restituisce il controllo.
Nota che sia with
e yield
stanno lavorando come previsto qui!Il punto di with lock
è quello di proteggere una risorsa e rimane protetto durante un yield
.Si può sempre esplicitamente il rilascio di questa protezione:
def safe_generator():
while True:
with lock():
# keep lock for critical operation
result = protected_operation()
# release lock before releasing control
yield result