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()
È stato utile?

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 eccezione GeneratorExit all'interno del generatore di interrompere l'iterazione. Alla ricezione di questa eccezione, il codice del generatore deve o aumentare GeneratorExit o StopIteration.

     

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 dichiarazioni try...finally in generatori possono essere garantiti per funzionare; la clausola finally ora sempre avere la possibilità di correre. Questo mi sembra un po 'minore del linguaggio curiosità, ma che utilizzano generatori e try...finally in realtà è necessaria al fine di attuare la dichiarazione with 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:

  1. 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 dopo yield è stata completata.

  2. 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top