一次打开多个(未指定数量)文件并确保它们正确关闭

tjm*_*tjm 20 python exception with-statement python-3.x

我知道我可以打开多个文件,比如

with open('a', 'rb') as a, open('b', 'rb') as b:
Run Code Online (Sandbox Code Playgroud)

但我有一个情况,我有一个文件列表打开,我想知道当文件数量提前未知时,首选方法是做什么的.就像是,

with [ open(f, 'rb') for f in files ] as fs:
Run Code Online (Sandbox Code Playgroud)

(但是AttributeError因为列表没有实现而失败了__exit__)

我不介意使用像

try:
    fs = [ open(f, 'rb') for f in files ]

    ....

finally:
    for f in fs:
        f.close()
Run Code Online (Sandbox Code Playgroud)

但是我不确定如果在尝试打开它们时抛出一些文件会发生什么.是否会fs在块中正确定义管理打开的文件finally

Dun*_*can 13

不,fs除非所有open()呼叫都成功完成,否则您的代码不会初始化.这应该工作:

fs = []
try:
    for f in files:
        fs.append(open(f, 'rb'))

    ....

finally:
    for f in fs:
        f.close()
Run Code Online (Sandbox Code Playgroud)

另请注意,f.close()可能会失败,因此您可能希望捕获并忽略(或以其他方式处理)任何失败.


Sin*_*ion 7

当然,为什么不呢,这是一个应该做的配方.创建一个可以输入任意数量的上下文的上下文管理器"池"(通过调用它的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
Run Code Online (Sandbox Code Playgroud)

例如:

>>> 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
>>> 
Run Code Online (Sandbox Code Playgroud)

注意事项:上下文管理器不应该在他们的__exit__()方法中引发异常,但如果他们这样做,这个方法不会对所有上下文管理器进行清理.类似地,即使每个上下文管理器都指示应该忽略异常(通过True从其退出方法返回),这仍然会允许引发异常.


tim*_*geb 5

ExitStack模块中的类contextlib提供您正在寻找的功能。文档中提到的规范用例是管理动态数量的文件。

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
Run Code Online (Sandbox Code Playgroud)