examples - manual de python para arcgis
Cómo usar un administrador de contexto de Python dentro de un generador (2)
En Python, ¿deberían usarse las sentencias with dentro de un generador? Para ser claros, no estoy preguntando sobre el uso de un decorador para crear un administrador de contexto desde una función de generador. Estoy preguntando si existe un problema inherente al usar una instrucción with como administrador de contexto dentro de un generador, ya que detectará las excepciones StopIteration
y GeneratorExit
en al menos algunos casos. Dos ejemplos siguen.
Un buen ejemplo del problema se presenta en el ejemplo de Beazley (página 106). Lo modifiqué para usar una sentencia with para que los archivos se cierren explícitamente después del rendimiento en el método de apertura. También he añadido dos formas en que se puede lanzar una excepción al iterar los resultados.
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
En este ejemplo, el administrador de contexto cierra con éxito los archivos en la función de apertura. Cuando se produce una excepción, veo el rastro de la excepción, pero el generador se detiene en silencio. Si el with-statement detecta la excepción, ¿por qué no continúa el generador?
Cuando defino mis propios administradores de contexto para usar dentro de un generador. Recibo errores de tiempo de ejecución que indican que he ignorado un GeneratorExit
. Por ejemplo:
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
Esta pequeña demostración funciona bien en el caso 1 sin excepciones, pero falla en el caso 2 donde se produce un error de atributo. Aquí veo una excepción RuntimeException
porque la instrucción with ha capturado e ignorado una excepción de GeneratorExit
.
¿Puede alguien ayudar a aclarar las reglas para este caso de uso difícil? Sospecho que es algo que estoy haciendo o no haciendo en mi método __exit__
. Intenté agregar código para volver a elevar GeneratorExit
, pero eso no ayudó.
de la entrada del modelo de datos para object.__exit__
Si se proporciona una excepción, y el método desea suprimir la excepción (es decir, evitar que se propague), debe devolver un valor verdadero. De lo contrario, la excepción se procesará normalmente al salir de este método.
En su función __exit__
, está devolviendo True
lo que suprimirá todas las excepciones. Si lo cambias para devolver False
, las excepciones continuarán aumentando normalmente (con la única diferencia de que garantizas que se __exit__
tu función __exit__
y puedes asegurarte de limpiar después de ti mismo)
Por ejemplo, cambiando el código a:
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: ''%s''; value: ''%s''" % (exctype, value)
if exctype is GeneratorExit:
return False
return True
le permite hacer lo correcto y no suprimir la GeneratorExit
del GeneratorExit
. Ahora solo se ve el error de atributo. Tal vez la regla de oro debería ser la misma que con cualquier manejo de Excepciones: solo intercepte las Excepciones si sabe cómo manejarlas . Tener un __exit__
return True
está a la par (¡quizás un poco peor!) Que tener un simple excepto:
try:
something()
except: #Uh-Oh
pass
Tenga en cuenta que cuando se genera (y no se captura) el AttributeError
, creo que eso hace que el recuento de referencia en su objeto generador caiga a 0, lo que luego activa una excepción de GeneratorExit
dentro del generador para que pueda limpiarse. Usando mi __exit__
, juegue con los siguientes dos casos y espero que vea lo que quiero decir:
try:
for item in foo(10):
print ''Fail - val: %d'' % item.val
item.not_an_attribute
except AttributeError:
pass
print "Here" #No reference to the generator left.
#Should see __exit__ before "Here"
y
g = foo(10)
try:
for item in g:
print ''Fail - val: %d'' % item.val
item.not_an_attribute
except AttributeError:
pass
print "Here"
b = g #keep a reference to prevent the reference counter from cleaning this up.
#Now we see __exit__ *after* "Here"
class CManager(object):
def __enter__(self):
print " __enter__"
return self
def __exit__(self, exctype, value, tb):
print " __exit__; excptype: ''%s''; value: ''%s''" % (exctype, value)
if exctype is None:
return
# only re-raise if it''s *not* the exception that was
# passed to throw(), because __exit__() must not raise
# an exception unless __exit__() itself failed. But throw()
# has to raise the exception to signal propagation, so this
# fixes the impedance mismatch between the throw() protocol
# and the __exit__() protocol.
#
if sys.exc_info()[1] is not (value or exctype()):
raise