web-dev-qa-db-fra.com

vérifier quels fichiers sont ouverts dans Python

Je reçois une erreur dans un programme qui est censé s'exécuter pendant trop longtemps car trop de fichiers sont ouverts. Existe-t-il un moyen de savoir quels fichiers sont ouverts afin que je puisse imprimer cette liste de temps en temps et voir où est le problème?

49
Claudiu

J'ai fini par encapsuler l'objet fichier intégré au point d'entrée de mon programme. J'ai découvert que je ne fermais pas mes enregistreurs.

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)
39
Claudiu

Pour répertorier tous les fichiers ouverts de manière multiplateforme, je recommanderais psutil .

#!/usr/bin/env python
import psutil

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

La question d'origine limite implicitement l'opération au processus en cours d'exécution, accessible via la classe Process de psutil.

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

Enfin, vous voudrez exécuter le code en utilisant un compte avec les autorisations appropriées pour accéder à ces informations ou vous pouvez voir des erreurs AccessDenied.

40
jkhines

Sous Linux, vous pouvez consulter le contenu de /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
23
Mike DeSimone

Bien que les solutions ci-dessus que le wrapper ouvre soient utiles pour son propre code, je déboguais mon client vers une bibliothèque tierce comprenant un code d'extension c, j'avais donc besoin d'une méthode plus directe. La routine suivante fonctionne sous darwin et (j'espère) d'autres environnements de type 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

Si quelqu'un peut s'étendre pour être portable sous Windows, je lui en serais reconnaissant.

14
shaunc

Sous Linux, vous pouvez utiliser lsof pour afficher tous les fichiers ouverts par un processus.

9
eduffy

Sous Windows, vous pouvez utiliser Process Explorer pour afficher tous les descripteurs de fichiers appartenant à un processus.

5
interjay

Comme dit précédemment, vous pouvez lister les fds sur Linux dans / proc/self/fd, voici une méthode simple pour les lister par programme:

import os
import sys
import errno

def list_fds():
    """List process currently open FDs and their target """
    if sys.platform != 'linux2':
        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
Romuald Brunet

La réponse acceptée présente certaines limites, car elle ne semble pas compter les tuyaux. J'avais un script python qui ouvrait de nombreux sous-processus et ne fermait pas correctement les canaux d'entrée, de sortie et d'erreur standard, qui étaient utilisés pour la communication. Si j'utilise la réponse acceptée, elle échouera à compter ces canaux ouverts comme des fichiers ouverts, mais (au moins sous Linux) ce sont des fichiers ouverts et comptent dans la limite des fichiers ouverts. La solution lsof -p suggérée par sumid et shunc fonctionne dans cette situation, car elle vous montre également les tuyaux ouverts.

3
stacksia

Obtenez une liste de tous les fichiers ouverts. handle.exe fait partie de Microsoft Sysinternals Suite . Une alternative est le psutil Python, mais je trouve que 'handle' imprimera plus de fichiers en cours d'utilisation.

Voici ce que j'ai fait. Avertissement de code 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)
2
Matt-the-Bat

Vous pouvez utiliser le script suivant. Il s'appuie sur Claudiu réponse . Il résout certains des problèmes et ajoute des fonctionnalités supplémentaires:

  • Imprime une trace de pile d'où le fichier a été ouvert
  • Imprime à la sortie du programme
  • Prise en charge des arguments de mots clés

Voici le code et un lien vers le Gist , qui est peut-être plus à jour.

"""
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()
1
chriscz

Je suppose que vous perdez des descripteurs de fichiers. Vous voudrez probablement parcourir votre code pour vous assurer que vous fermez tous les fichiers que vous ouvrez.

1
Adam Crossland