【问题标题】:check what files are open in Python检查在 Python 中打开了哪些文件
【发布时间】:2011-01-02 16:13:29
【问题描述】:

我在一个应该运行很长时间的程序中遇到错误,因为打开了太多文件。有什么方法可以让我跟踪哪些文件是打开的,这样我就可以偶尔打印该列表并查看问题出在哪里?

【问题讨论】:

标签: python debugging exception file


【解决方案1】:

要以跨平台方式列出所有打开的文件,我会推荐psutil

#!/usr/bin/env python
import psutil

for proc in psutil.process_iter():
    print(proc.open_files())

原题隐式限制了对当前运行的进程的操作,可以通过psutil的Process类访问。

proc = psutil.Process()
print(proc.open_files())

最后,您需要使用具有相应权限的帐户运行代码以访问此信息,否则您可能会看到 AccessDenied 错误。

【讨论】:

  • 这似乎只适用于基于磁盘的文件,不适用于套接字、fifos 等。
  • @NeilenMarais:它确实适用于套接字,请参阅pypi.python.org/pypi/psutil中的示例
  • 新版本的 psutil 使用名称proc.get_open_files( )
  • 对我根本不起作用。简单测试:from psutil import Process; proc = Process(); with open('fileincurrentdirectory') as f: print(proc.open_files()) - 您需要添加换行符并缩进最后的打印语句。它会打印出一个空列表。而且我确实使用了当前目录中实际文件的名称。
【解决方案2】:

如前所述,您可以在 /proc/self/fd 中列出 Linux 上的 fds,这是一种以编程方式列出它们的简单方法:

import os
import sys
import errno

def list_fds():
    """List process currently open FDs and their target """
    if not sys.platform.startswith('linux'):
        raise NotImplementedError('Unsupported platform: %s' % sys.platform)

    ret = {}
    base = '/proc/self/fd'
    for num in os.listdir(base):
        path = None
        try:
            path = os.readlink(os.path.join(base, num))
        except OSError as err:
            # Last FD is always the "listdir" one (which may be closed)
            if err.errno != errno.ENOENT:
                raise
        ret[int(num)] = path

    return ret

【讨论】:

    【解决方案3】:

    我最终在程序的入口点包装了内置文件对象。我发现我没有关闭我的记录器。

    import io
    import sys
    import builtins
    import traceback
    from functools import wraps
    
    
    def opener(old_open):
        @wraps(old_open)
        def tracking_open(*args, **kw):
            file = old_open(*args, **kw)
    
            old_close = file.close
            @wraps(old_close)
            def close():
                old_close()
                open_files.remove(file)
            file.close = close
            file.stack = traceback.extract_stack()
    
            open_files.add(file)
            return file
        return tracking_open
    
    
    def print_open_files():
        print(f'### {len(open_files)} OPEN FILES: [{", ".join(f.name for f in open_files)}]', file=sys.stderr)
        for file in open_files:
            print(f'Open file {file.name}:\n{"".join(traceback.format_list(file.stack))}', file=sys.stderr)
    
    
    open_files = set()
    io.open = opener(io.open)
    builtins.open = opener(builtins.open)
    

    【讨论】:

    • @Claudiu - 请问如何使用关闭所有打开的文件 - def closeall(self): print "### CLOSING All files ###" oldfile.close(self,(fx for f in openfiles)) openfiles.remove(self,(fx for f in openfiles)) 会完美运行吗?
    • 为了不延长文件对象的生命周期(并因此防止在 cpython 中自动关闭引用计数的对象),IMO 值得使用 weakref.WeakSet 而不是普通的 set for openfiles .
    • 谢谢,对我也很出色。请注意,我在也使用 sklearn joblib 修补程序时遇到了异常:同一个文件可能会被 joblib.load 关闭两次,导致 openfiles.remove(self) 出现异常。
    • 当我使用这个 sn-p 时,我得到下一个错误: AttributeError: 'file' object attribute 'close' is read-only。知道如何解决吗? $python --version Python 2.7.10
    【解决方案4】:

    您可以使用以下脚本。它建立在 Claudiu 的 answer 之上。它解决了一些问题并添加了其他功能:

    • 打印文件打开位置的堆栈跟踪
    • 程序退出时打印
    • 关键字参数支持

    这是代码和gist 的链接,它可能是最新的。

    """
    Collect stacktraces of where files are opened, and prints them out before the
    program exits.
    
    Example
    ========
    
    monitor.py
    ----------
    from filemonitor import FileMonitor
    FileMonitor().patch()
    f = open('/bin/ls')
    # end of monitor.py
    
    $ python monitor.py
      ----------------------------------------------------------------------------
      path = /bin/ls
      >   File "monitor.py", line 3, in <module>
      >     f = open('/bin/ls')
      ----------------------------------------------------------------------------
    
    Solution modified from:
    https://stackoverflow.com/questions/2023608/check-what-files-are-open-in-python
    """
    from __future__ import print_function
    import __builtin__
    import traceback
    import atexit
    import textwrap
    
    
    class FileMonitor(object):
    
        def __init__(self, print_only_open=True):
            self.openfiles = []
            self.oldfile = __builtin__.file
            self.oldopen = __builtin__.open
    
            self.do_print_only_open = print_only_open
            self.in_use = False
    
            class File(self.oldfile):
    
                def __init__(this, *args, **kwargs):
                    path = args[0]
    
                    self.oldfile.__init__(this, *args, **kwargs)
                    if self.in_use:
                        return
                    self.in_use = True
                    self.openfiles.append((this, path, this._stack_trace()))
                    self.in_use = False
    
                def close(this):
                    self.oldfile.close(this)
    
                def _stack_trace(this):
                    try:
                        raise RuntimeError()
                    except RuntimeError as e:
                        stack = traceback.extract_stack()[:-2]
                        return traceback.format_list(stack)
    
            self.File = File
    
        def patch(self):
            __builtin__.file = self.File
            __builtin__.open = self.File
    
            atexit.register(self.exit_handler)
    
        def unpatch(self):
            __builtin__.file = self.oldfile
            __builtin__.open = self.oldopen
    
        def exit_handler(self):
            indent = '  > '
            terminal_width = 80
            for file, path, trace in self.openfiles:
                if file.closed and self.do_print_only_open:
                    continue
                print("-" * terminal_width)
                print("  {} = {}".format('path', path))
                lines = ''.join(trace).splitlines()
                _updated_lines = []
                for l in lines:
                    ul = textwrap.fill(l,
                                       initial_indent=indent,
                                       subsequent_indent=indent,
                                       width=terminal_width)
                    _updated_lines.append(ul)
                lines = _updated_lines
                print('\n'.join(lines))
                print("-" * terminal_width)
                print()
    

    【讨论】:

      【解决方案5】:

      获取所有打开文件的列表。 handle.exe 是 Microsoft 的 Sysinternals Suite 的一部分。另一种选择是 psutil Python 模块,但我发现“句柄”会打印出更多正在使用的文件。

      这是我做的。 Kludgy 代码警告。

      #!/bin/python3
      # coding: utf-8
      """Build set of files that are in-use by processes.
         Requires 'handle.exe' from Microsoft SysInternals Suite.
         This seems to give a more complete list than using the psutil module.
      """
      
      from collections import OrderedDict
      import os
      import re
      import subprocess
      
      # Path to handle executable
      handle = "E:/Installers and ZIPs/Utility/Sysinternalssuite/handle.exe"
      
      # Get output string from 'handle'
      handle_str = subprocess.check_output([handle]).decode(encoding='ASCII')
      
      """ Build list of lists.
          1. Split string output, using '-' * 78 as section breaks.
          2. Ignore first section, because it is executable version info.
          3. Turn list of strings into a list of lists, ignoring first item (it's empty).
      """
      work_list = [x.splitlines()[1:] for x in handle_str.split(sep='-' * 78)[1:]]
      
      """ Build OrderedDict of pid information.
          pid_dict['pid_num'] = ['pid_name','open_file_1','open_file_2', ...]
      """
      pid_dict = OrderedDict()
      re1 = re.compile("(.*?\.exe) pid: ([0-9]+)")  # pid name, pid number
      re2 = re.compile(".*File.*\s\s\s(.*)")  # File name
      for x_list in work_list:
          key = ''
          file_values = []
          m1 = re1.match(x_list[0])
          if m1:
              key = m1.group(2)
      #        file_values.append(m1.group(1))  # pid name first item in list
      
          for y_strings in x_list:
              m2 = re2.match(y_strings)
              if m2:
                  file_values.append(m2.group(1))
          pid_dict[key] = file_values
      
      # Make a set of all the open files
      values = []
      for v in pid_dict.values():
          values.extend(v)
      files_open = sorted(set(values))
      
      txt_file = os.path.join(os.getenv('TEMP'), 'lsof_handle_files')
      
      with open(txt_file, 'w') as fd:
          for a in sorted(files_open):
              fd.write(a + '\n')
      subprocess.call(['notepad', txt_file])
      os.remove(txt_file)
      

      【讨论】:

        【解决方案6】:

        接受的响应有一些限制,因为它似乎不计算管道。我有一个 python 脚本,它打开了许多子进程,但未能正确关闭用于通信的标准输入、输出和错误管道。如果我使用接受的响应,它将无法将这些打开的管道计为打开的文件,但(至少在 Linux 中)它们是打开的文件并计入打开文件限制。 sumid 和 shunc 建议的lsof -p 解决方案适用于这种情况,因为它还显示了打开的管道。

        【讨论】:

          【解决方案7】:

          虽然上述 wrap opens 的解决方案对自己的代码很有用,但我正在将我的客户端调试到包含一些 c 扩展代码的第三方库,所以我需要一种更直接的方法。以下例程在 darwin 和(我希望)其他类 unix 环境下工作:

          def get_open_fds():
              '''
              return the number of open file descriptors for current process
          
              .. warning: will only work on UNIX-like os-es.
              '''
              import subprocess
              import os
          
              pid = os.getpid()
              procs = subprocess.check_output( 
                  [ "lsof", '-w', '-Ff', "-p", str( pid ) ] )
          
              nprocs = len( 
                  filter( 
                      lambda s: s and s[ 0 ] == 'f' and s[1: ].isdigit(),
                      procs.split( '\n' ) )
                  )
              return nprocs
          

          如果有人可以扩展到可移植到 Windows,我将不胜感激。

          【讨论】:

            【解决方案8】:

            在Linux上,可以看/proc/self/fd的内容:

            $ ls -l /proc/self/fd/
            total 0
            lrwx------ 1 foo users 64 Jan  7 15:15 0 -> /dev/pts/3
            lrwx------ 1 foo users 64 Jan  7 15:15 1 -> /dev/pts/3
            lrwx------ 1 foo users 64 Jan  7 15:15 2 -> /dev/pts/3
            lr-x------ 1 foo users 64 Jan  7 15:15 3 -> /proc/9527/fd
            

            【讨论】:

            • 这仅适用于 CPython 还是所有实现?我记得我认为在 ipython 中打开的文件列在/proc/ipython_pid/fd/ 中。另外,在上面的列表中,您如何知道您打开了哪些文件以及 Python 打开了哪些文件(以及您不应该关闭哪些文件)?
            • 这适用于提供 /proc 文件系统的 Linux 系统。它独立于语言;可以访问/proc 中“文件”的任何语言的任何程序都可以获得此信息。 ipython我没弄乱过,但基本思路是在初始化后记录/proc/self/fd的内容,然后在运行后期比较内容寻找变化。
            【解决方案9】:

            在 Linux 上,您可以使用 lsof 显示进程打开的所有文件。

            【讨论】:

            • 有python的lsof的一些内部函数,还是我真的要调用linux lsof?
            【解决方案10】:

            在 Windows 上,您可以使用 Process Explorer 显示进程拥有的所有文件句柄。

            【讨论】:

              【解决方案11】:

              我猜你正在泄漏文件描述符。您可能需要查看您的代码以确保您正在关闭所有打开的文件。

              【讨论】:

              • 我认为这就是问题所在。但是代码非常复杂,这将是一种立即发现哪些文件没有被关闭的简单方法。
              猜你喜欢
              • 1970-01-01
              • 2012-08-08
              • 1970-01-01
              • 1970-01-01
              • 2011-10-13
              • 1970-01-01
              • 1970-01-01
              • 2010-12-07
              • 2013-12-05
              相关资源
              最近更新 更多