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

发布于 2024-12-12 20:38:32 字数 555 浏览 4 评论 0原文

我知道我可以使用类似的方法打开多个文件,

with open('a', 'rb') as a, open('b', 'rb') as b:

但我有一种情况,我有一个要打开的文件列表,并且想知道当文件数量事先未知时执行相同操作的首选方法是什么。类似的东西

with [ open(f, 'rb') for f in files ] as fs:

(但是由于列表没有实现__exit__,所以失败并出现AttributeError

我不介意使用类似的东西,

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

    ....

finally:
    for f in fs:
        f.close()

但我不确定如果某些尝试打开文件时抛出文件。 fs 是否会在 finally 块中正确定义,并包含成功打开的文件?

I am aware that I can open multiple files with something like,

with open('a', 'rb') as a, open('b', 'rb') as b:

But I have a situation where I have a list of files to open and am wondering what the preferred method is of doing the same when the number of files is unknown in advance. Something like,

with [ open(f, 'rb') for f in files ] as fs:

(but this fails with an AttributeError since list doesn't implement __exit__)

I don't mind using something like,

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

    ....

finally:
    for f in fs:
        f.close()

But am not sure what will happen if some files throw when trying to open them. Will fs be properly defined, with the files that did manage to open, in the finally block?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

太阳哥哥 2024-12-19 20:38:32

不,除非所有 open() 调用成功完成,否则您的代码不会初始化 fs。但这应该可以工作:

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

    ....

finally:
    for f in fs:
        f.close()

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

No, your code wouldn't initialise fs unless all open() calls completed successfully. This should work though:

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

    ....

finally:
    for f in fs:
        f.close()

Note also that f.close() could fail so you may want to catch and ignore (or otherwise handle) any failures there.

叫思念不要吵 2024-12-19 20:38:32

当然,为什么不呢,这是一个可以做到这一点的食谱。创建一个上下文管理器“池”,它可以输入任意数量的上下文(通过调用它的 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),这仍然允许引发异常。

Sure, why not, Here's a recipe that should do it. Create a context manager 'pool' that can enter an arbitrary number of contexts (by calling it's enter() method) and they will be cleaned up at the end of the end of the suite.

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

For example:

>>> 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
>>> 

Caveats: context managers aren't supposed to raise exceptions in their __exit__() methods, but if they do, this recipe doesn't do the cleanup for all the context managers. Similarly, even if every context manager indicates that an exception should be ignored (by returning True from their exit methods), this will still allow the exception to be raised.

中二柚 2024-12-19 20:38:32

ExitStack 来自 < href="https://docs.python.org/3/library/contextlib.html#module-contextlib" rel="noreferrer">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

The class ExitStack from the contextlib module provides the functionality you are looking for.
The canonical use-case that is mentioned in the documentation is managing a dynamic number of files.

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
小霸王臭丫头 2024-12-19 20:38:32

尝试打开文件、尝试读取文件以及(极少数情况下)尝试关闭文件时可能会发生错误。

因此,基本的错误处理结构可能如下所示:

try:
    stream = open(path)
    try:
        data = stream.read()
    finally:
        stream.close()
except EnvironmentError as exception:
    print 'ERROR:', str(exception)
else:
    print 'SUCCESS'
    # process data

这确保如果 stream 变量存在,则始终会调用 close。如果 stream 不存在,则 open 一定失败,因此没有要关闭的文件(在这种情况下, except 块将立即执行)。

您真的需要并行打开文件,还是可以顺序处理它们?如果是后者,则应将类似上述文件处理代码的内容放入一个函数中,然后为列表中的每个路径调用该函数。

Errors can occur when attempting to open a file, when attempting to read from a file, and (very rarely) when attempting to close a file.

So a basic error handling structure might look like:

try:
    stream = open(path)
    try:
        data = stream.read()
    finally:
        stream.close()
except EnvironmentError as exception:
    print 'ERROR:', str(exception)
else:
    print 'SUCCESS'
    # process data

This ensures that close will always be called if the stream variable exists. If stream doesn't exist, then open must have failed, and so there is no file to close (in which case, the except block will be executed immediately).

Do you really need to have the files open in parallel, or can they be processed sequentially? If the latter, then something like the above file-processing code should be put in a function, which is then called for each path in the list.

吃→可爱长大的 2024-12-19 20:38:32

感谢您的所有回答。受到大家的启发,我提出了以下建议。我认为(希望)它能按我的预期工作。我不确定是否将其作为问题的答案或补充发布,但认为答案更合适,因为如果它未能满足我的要求,可以对其进行适当的评论。

它可以像这样使用..

with contextlist( [open, f, 'rb'] for f in files ) as fs:
    ....

或像这样..

f_lock = threading.Lock()
with contextlist( f_lock, ([open, f, 'rb'] for f in files) ) as (lock, *fs):
    ....

在这里,

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>   '))

Thanks for all your answers. Taking inspiration from all of you, I have come up with the following. I think (hope) it works as I intended. I wasn't sure whether to post it as an answer or an addition to the question, but thought an answer was more appropriate as then if it fails to do what I'd asked it can be commented on appropriately.

It can be used for example like this ..

with contextlist( [open, f, 'rb'] for f in files ) as fs:
    ....

or like this ..

f_lock = threading.Lock()
with contextlist( f_lock, ([open, f, 'rb'] for f in files) ) as (lock, *fs):
    ....

And here it is,

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>   '))
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文