In python, le istruzioni with devono essere utilizzate all'interno di un generatore? Per essere chiari, non sto chiedendo di usare un decoratore per creare un gestore di contesto da una funzione generatore. Sto chiedendo se c'è un problema inerente usando un'istruzione with come un gestore di contesto all'interno di un generatore in quanto prenderà le eccezioni StopIteration
e GeneratorExit
in almeno alcuni casi. Seguono due esempi.Come utilizzare un gestore di contesto python all'interno di un generatore
Un buon esempio del problema viene sollevato dall'esempio di Beazley (pagina 106). L'ho modificato per utilizzare un'istruzione with in modo che i file vengano chiusi esplicitamente dopo il rendimento nel metodo di apertura. Ho anche aggiunto due modi in cui un'eccezione può essere lanciata mentre si ripetono i risultati.
import os
import fnmatch
def find_files(topdir, pattern):
for path, dirname, filelist in os.walk(topdir):
for name in filelist:
if fnmatch.fnmatch(name, pattern):
yield os.path.join(path,name)
def opener(filenames):
f = None
for name in filenames:
print "F before open: '%s'" % f
#f = open(name,'r')
with open(name,'r') as f:
print "Fname: %s, F#: %d" % (name, f.fileno())
yield f
print "F after yield: '%s'" % f
def cat(filelist):
for i,f in enumerate(filelist):
if i ==20:
# Cause and exception
f.write('foobar')
for line in f:
yield line
def grep(pattern,lines):
for line in lines:
if pattern in line:
yield line
pylogs = find_files("/var/log","*.log*")
files = opener(pylogs)
lines = cat(files)
pylines = grep("python", lines)
i = 0
for line in pylines:
i +=1
if i == 10:
raise RuntimeError("You're hosed!")
print 'Counted %d lines\n' % i
In questo esempio, il gestore di contesto chiude correttamente i file nella funzione di apertura. Quando viene sollevata un'eccezione, rilevo la traccia dall'eccezione, ma il generatore si blocca silenziosamente. Se l'istruzione with ottiene l'eccezione, perché il generatore non continua?
Quando definisco i miei gestori di contesto da utilizzare all'interno di un generatore. Ottengo errori di runtime dicendo che ho ignorato un GeneratorExit
. Per esempio:
class CManager(object):
def __enter__(self):
print " __enter__"
return self
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
return True
def foo(n):
for i in xrange(n):
with CManager() as cman:
cman.val = i
yield cman
# Case1
for item in foo(10):
print 'Pass - val: %d' % item.val
# Case2
for item in foo(10):
print 'Fail - val: %d' % item.val
item.not_an_attribute
Questo piccolo demo funziona bene in case1 senza eccezioni sollevate, ma non riesce a case2 in cui viene generato un errore attributo. Qui vedo un RuntimeException
sollevato perché l'istruzione with ha catturato e ignorato un'eccezione GeneratorExit
.
Qualcuno può aiutare a chiarire le regole per questo astuto caso d'uso? Sospetto che sia qualcosa che sto facendo o non sto facendo nel mio metodo __exit__
. Ho provato ad aggiungere codice per controrilanciare GeneratorExit
, ma quello non ha aiutato.
Questa è una risposta impressionante! – lukecampbell
@mgilson Grazie per la tua grande risposta. Sembra che la funzione del generatore non sia ciò che cattura l'errore di attributo. Questo è il comportamento che volevo, ma sembra che non sia possibile. Voglio usare una sola istruzione per centralizzare la gestione delle eccezioni in una sequenza di generatori. – David
@David - i context manager sono qualcosa che ho lavorato con una quantità decente negli ultimi tempi, quindi sono freschi nella mia mente :). E 'stata una buona domanda. Auguro a tutti i nuovi utenti di porre queste belle prime domande :). – mgilson