para examples ejemplos python generator with-statement

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