【问题标题】:How can I open multiple files (number of files unknown beforehand) using "with open" statement?如何使用“with open”语句打开多个文件(事先未知的文件数)?
【发布时间】:2014-03-07 23:27:00
【问题描述】:

我特别需要使用with open 语句来打开文件,因为我需要同时打开几百个文件并使用K-way merge 将它们合并。我理解,理想情况下我应该将 K 保持在较低水平,但我没有预见到这个问题。

现在不能从头开始,因为我有一个截止日期。所以在这一点上,我需要非常快速的 I/O,它不会将文件的整个/巨大部分存储在内存中(因为有数百个文件,每个 ~10MB)。我只需要一次读取一行即可进行 K 路合并。减少内存使用是我现在的主要关注点。

我了解到with open 是最有效的技术,但我无法理解如何将open 的所有文件放在一个with open 语句中。原谅我的初学者无知!

更新:此问题已解决。事实证明,问题根本不在于我是如何打开文件的。我发现过多的内存使用是由于垃圾收集效率低下造成的。我根本没有使用with open。我使用了常规的f=open()f.close()。垃圾收集拯救了这一天。

【问题讨论】:

  • 我不认为with openf = open() ... close(f) 更有效。它应该更方便,但听起来您的情况并非如此。您可以只打开一堆文件并将文件对象保存在一个列表中。
  • with 语句与效率无关,它只是确保文件将被隐式关闭(即使发生异常)。正如@MarkkuK 所说,您可以将文件对象的引用存储在列表中,然后在最后手动关闭它们,并使用 try-finally 块来确保即使发生错误也关闭文件。
  • 感谢两位的快速响应。您能否建议一个替代方案,即使我一起打开几百个文件,内存使用量也不会那么高?由于我一次只需要从这些文件中提取一行,我认为如果我使用一些不会在缓冲区中加载大部分文件的技术会有所帮助。
  • 因此,您想先从第一个文件中读取所有行,然后再从第二行...等等,或者您想从每个文件中读取第一行,然后从每个文件中读取第二行...。 ?
  • 使用文件对象的readline-方法读取单行。重复使用将每次检索下一行:f = open(file); f.readline(); f.readline(); ...; f.close()

标签: python file python-2.7 merge with-statement


【解决方案1】:

通过使用内置的contextmanger 函数装饰器来定义“with 语句上下文管理器的工厂函数”,就像文档中所说的那样,编写自己的上下文管理器来处理这个问题相当容易。例如:

from contextlib import contextmanager

@contextmanager
def multi_file_manager(files, mode='rt'):
    """ Open multiple files and make sure they all get closed. """
    files = [open(file, mode) for file in files]
    yield files
    for file in files:
        file.close()


if __name__ == '__main__':

    filenames = 'file1', 'file2', 'file3'

    with multi_file_manager(filenames) as files:
        a = files[0].readline()
        b = files[2].readline()
            ...

如果您不提前知道所有文件,那么创建一个支持在上下文中增量添加它们的上下文管理器同样容易。在下面的代码中,contextlib.ContextDecorator 被用作基类来简化MultiFileManager 类的实现。

from contextlib import ContextDecorator

class MultiFileManager(ContextDecorator):
    def __init__(self, files=None):
        self.files = [] if files is None else files

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for file in self.files:
            file.close()

    def __iadd__(self, other):
        """Add file to be closed when leaving context."""
        self.files.append(other)
        return self


if __name__ == '__main__':

    filenames = 'mfm_file1.txt', 'mfm_file2.txt', 'mfm_file3.txt'

    with MultiFileManager() as mfmgr:
        for count, filename in enumerate(filenames, start=1):
            file = open(filename, 'w')
            mfmgr += file  # Add file to be closed later.
            file.write(f'this is file {count}\n')

【讨论】:

    【解决方案2】:

    虽然不是 2.7 的解决方案,但我应该指出,对于 3.3+,contextlib.ExitStack 有一个好的、正确的解决方案,它可以用来正确地做到这一点(当你自己滚动)并且很好:

    from contextlib import ExitStack
    
    with open('source_dataset.txt') as src_file, ExitStack() as stack:
        files = [stack.enter_context(open(fname, 'w')) for fname in fname_list]
        ... do stuff with src_file and the values in files ...
    ... src_file and all elements in stack cleaned up on block exit ...
    

    重要的是,如果任何opens 失败,那么在该点之前成功的所有opens 都将被确定性地清除;在这种情况下,大多数天真的解决方案最终都无法清理,最多只能依赖垃圾收集器,而在没有对象可收集的锁获取等情况下,无法释放锁。

    由于此问题被标记为未指定 Python 版本的 duplicate 的“原始”问题,因此在此处发布。

    【讨论】:

      【解决方案3】:
      with open(...) as f: 
          # do stuff 
      

      大致翻译成

      f = open(...)
      # do stuff
      f.close()
      

      在您的情况下,我不会使用 with open 语法。如果您有文件名列表,请执行以下操作

      filenames = os.listdir(file_directory)
      open_files = map(open, filenames)
      # do stuff
      for f in open_files:
          f.close()
      

      如果您真的想使用with open 语法,您可以制作自己的上下文管理器来接受文件名列表

      class MultipleFileManager(object):
          def __init__(self, files):
              self.files = files
      
          def __enter__(self):
              self.open_files = map(open, self.files)
              return self.open_files
      
          def __exit__(self):
              for f in self.open_files:
                  f.close()
      

      然后像这样使用它:

      filenames = os.listdir(file_directory)
      with MulitpleFileManager(filenames) as files:
          for f in files:
              # do stuff
      

      我认为在这种情况下使用上下文管理器的唯一优势是您不会忘记关闭文件。但是手动关闭文件并没有错。请记住,当您的程序退出时,操作系统将回收其资源。

      【讨论】:

      • 你为什么要创建Nones 列表只是为了关闭文件?
      • @AshwiniChaudhary 你说的无列表是什么意思?
      • map(lambda x: x.close(), self.open_files)
      • @AshwiniChaudhary 啊,不错的收获。将其更改为 for 循环。虽然看讨论我不认为我的答案是他们想要的
      猜你喜欢
      • 2012-03-06
      • 2011-06-04
      • 2020-03-16
      • 2018-10-29
      • 2014-01-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多