一次打开多个文件并确保正确关闭
我知道可以用类似下面的方式打开多个文件,
with open('a', 'rb') as a, open('b', 'rb') as b:
但是我现在有一个情况是,我有一个文件列表想要打开,但我不知道文件的数量,所以想知道有什么好的方法来处理这种情况。比如说,
with [ open(f, 'rb') for f in files ] as fs:
(不过这样会出错,出现AttributeError
,因为列表不支持__exit__
这个功能)
我也可以用类似下面的方式,
try:
fs = [ open(f, 'rb') for f in files ]
....
finally:
for f in fs:
f.close()
但我不太确定如果有些文件在打开的时候出错,会发生什么。那时候fs
会不会在finally
块里正确地定义,包含那些成功打开的文件呢?
5 个回答
5
来自contextlib
模块的类ExitStack
提供了你所需要的功能。文档中提到的典型用法是处理动态数量的文件。
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
7
当然可以,这里有一个方法可以实现这个功能。你可以创建一个叫做'pool'的上下文管理器,它可以进入任意数量的上下文(通过调用它的enter()
方法),并且在结束时会自动清理这些上下文。
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
比如说:
>>> 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
>>>
注意事项:上下文管理器在它们的__exit__()
方法中不应该抛出异常,但如果真的抛出了,这个方法就无法为所有的上下文管理器进行清理。同样,即使每个上下文管理器都表示应该忽略异常(通过在它们的退出方法中返回True
),这仍然会导致异常被抛出。
14
不,你的代码不会初始化 fs
,除非所有的 open()
调用都成功完成。不过,下面的代码应该可以正常工作:
fs = []
try:
for f in files:
fs.append(open(f, 'rb'))
....
finally:
for f in fs:
f.close()
另外,注意 f.close()
也可能会失败,所以你可能需要处理这些失败,或者干脆忽略它们。