plot title python
Abriendo múltiples(un número no especificado) de archivos a la vez y asegurando que estén cerrados correctamente (5)
Claro, por qué no, aquí hay una receta que debe hacerlo. Cree un ''grupo'' de gestores de contexto que pueda ingresar un número arbitrario de contextos (al llamar al método enter()
) y se limpiarán al final del paquete.
class ContextPool(object):
def __init__(self):
self._pool = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_tb):
for close in reversed(self._pool):
close(exc_type, exc_value, exc_tb)
def enter(self, context):
close = context.__exit__
result = context.__enter__()
self._pool.append(close)
return result
Por ejemplo:
>>> class StubContextManager(object):
... def __init__(self, name):
... self.__name = name
... def __repr__(self):
... return "%s(%r)" % (type(self).__name__, self.__name)
...
... def __enter__(self):
... print "called %r.__enter__()" % (self)
...
... def __exit__(self, *args):
... print "called %r.__exit__%r" % (self, args)
...
>>> with ContextPool() as pool:
... pool.enter(StubContextManager("foo"))
... pool.enter(StubContextManager("bar"))
... 1/0
...
called StubContextManager(''foo'').__enter__()
called StubContextManager(''bar'').__enter__()
called StubContextManager(''bar'').__exit__(<type ''exceptions.ZeroDivisionError''>, ZeroDivisionError(''integer division or modulo by zero'',), <traceback object at 0x02958648>)
called StubContextManager(''foo'').__exit__(<type ''exceptions.ZeroDivisionError''>, ZeroDivisionError(''integer division or modulo by zero'',), <traceback object at 0x02958648>)
Traceback (most recent call last):
File "<pyshell#67>", line 4, in <module>
1/0
ZeroDivisionError: integer division or modulo by zero
>>>
Advertencias: los administradores de contexto no deben generar excepciones en sus __exit__()
, pero si lo hacen, esta receta no hace la limpieza para todos los administradores de contexto. De forma similar, incluso si cada administrador de contexto indica que se debe ignorar una excepción (devolviendo True
de sus métodos de salida), esto permitirá que se genere la excepción.
Soy consciente de que puedo abrir varios archivos con algo como:
with open(''a'', ''rb'') as a, open(''b'', ''rb'') as b:
Pero tengo una situación en la que tengo una lista de archivos para abrir y me pregunto cuál es el método preferido para hacer lo mismo cuando se desconoce de antemano la cantidad de archivos. Algo como,
with [ open(f, ''rb'') for f in files ] as fs:
(pero esto falla con un AttributeError
ya que la lista no implementa __exit__
)
No me importa usar algo como
try:
fs = [ open(f, ''rb'') for f in files ]
....
finally:
for f in fs:
f.close()
Pero no estoy seguro de qué sucederá si algunos archivos se lanzan al intentar abrirlos. ¿Se definirá correctamente, con los archivos que se lograron abrir, en el bloque final?
Gracias por todas sus respuestas. Tomando inspiración de todos ustedes, se me ocurrió lo siguiente. Creo (espero) que funcione como pretendía. No estaba seguro de si publicarlo como una respuesta o una adición a la pregunta, pero pensé que una respuesta era más apropiada, ya que si fallaba en hacer lo que le había pedido, se puede comentar apropiadamente.
Se puede usar por ejemplo como este ...
with contextlist( [open, f, ''rb''] for f in files ) as fs:
....
o así ..
f_lock = threading.Lock()
with contextlist( f_lock, ([open, f, ''rb''] for f in files) ) as (lock, *fs):
....
Y aquí está,
import inspect
import collections
import traceback
class contextlist:
def __init__(self, *contexts):
self._args = []
for ctx in contexts:
if inspect.isgenerator(ctx):
self._args += ctx
else:
self._args.append(ctx)
def __enter__(self):
if hasattr(self, ''_ctx''):
raise RuntimeError("cannot reenter contextlist")
s_ctx = self._ctx = []
try:
for ctx in self._args:
if isinstance(ctx, collections.Sequence):
ctx = ctx[0](*ctx[1:])
s_ctx.append(ctx)
try:
ctx.__enter__()
except Exception:
s_ctx.pop()
raise
return s_ctx
except:
self.__exit__()
raise
def __exit__(self, *exc_info):
if not hasattr(self, ''_ctx''):
raise RuntimeError("cannot exit from unentered contextlist")
e = []
for ctx in reversed(self._ctx):
try:
ctx.__exit__()
except Exception:
e.append(traceback.format_exc())
del self._ctx
if not e == []:
raise Exception(''/n> ''*2+(''''.join(e)).replace(''/n'',''/n> ''))
La clase ExitStack
del módulo contextlib
proporciona la funcionalidad que está buscando. El caso de uso canónico que se menciona en la documentación es administrar un número dinámico de archivos.
with ExitStack() as stack:
files = [stack.enter_context(open(fname)) for fname in filenames]
# All opened files will automatically be closed at the end of
# the with statement, even if attempts to open files later
# in the list raise an exception
No, su código no inicializaría fs
menos que todas open()
llamadas open()
se completaran con éxito. Esto debería funcionar sin embargo:
fs = []
try:
for f in files:
fs.append(open(f, ''rb''))
....
finally:
for f in fs:
f.close()
Tenga en cuenta también que f.close () podría fallar, por lo que es posible que desee detectar e ignorar (o manejar de otro modo) cualquier falla allí.
Se pueden producir errores al intentar abrir un archivo, al intentar leer un archivo y (muy raramente) al intentar cerrar un archivo.
Entonces, una estructura básica de manejo de errores podría verse como:
try:
stream = open(path)
try:
data = stream.read()
finally:
stream.close()
except EnvironmentError as exception:
print ''ERROR:'', str(exception)
else:
print ''SUCCESS''
# process data
Esto asegura que siempre se llamará a close
si existe la variable de stream
. Si la stream
no existe, entonces la open
debe haber fallado, por lo que no hay ningún archivo para cerrar (en cuyo caso, el bloque de excepción se ejecutará de inmediato).
¿Realmente necesita tener los archivos abiertos en paralelo, o pueden procesarse secuencialmente? Si es lo último, entonces algo como el código de procesamiento de archivos anterior se debe poner en una función, que luego se llama para cada ruta en la lista.