Есть ли простой способ найти, какая часть кода не закрывает файл

#python #file

#python #файл

Вопрос:

У меня большая программа с большим объемом кода. И это открывает файл, но не закрывает его.

Вопрос:

Есть ли простой способ узнать, где это происходит?

Подробнее:

OS — Linux
Python — 2.7

Почему это важно? Представьте ситуацию:

 df -h 
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda5       157G   39G  110G  27% /
 

Доступно 110 G. Давайте создадим большой файл

 fallocate -l 10G large_file.csv
 

Теперь доступно 100 G

 df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda5       157G   49G  100G  34% /
 

давайте напишем программу, которая открывает файл, и запустим ее:

 import time
f = open('large_file.csv')
try:
    while True:
        time.sleep(1)
except:
    pass
 

Пока это выполняется, давайте удалим файл:

 rm large_file.csv
 

Проверка пробела:

 df -h 
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda5       157G   49G  100G  34% /
 

Вы видите, что он все еще 100G доступен.

Итак, вопрос в том, как легко найти проблемы такого типа в большой программе?

Комментарии:

1. Для этого и предназначена lsof команда.

2. Просто используйте with инструкцию или try finally для работы с файлами, чтобы вы никогда не забывали их закрывать.

3. @BenjaminBannier: вы можете разорвать связь с открытым файлом; файл все еще существует до закрытия . Пропала запись каталога. Это сделано специально .

4. Возможно, вы могли бы заменить значение по умолчанию open в __builtin__ модуле оболочкой, которая отслеживает, где они происходят.

5. @Vor: вы можете заменить значение по умолчанию open в __builtin__ модуле, как предложил martineau, чтобы вызвать исключение, перехватить исключение и использовать inspect модуль для поиска AST из трассировки, чтобы узнать open , предшествует ли вызову with try/finally блок или ( getouterframes ) , если нет, вы можете найти соответствующие блоки кода. Это интересная проблема, но, к сожалению, мне не хватает времени, чтобы опубликовать правильный ответ.

Ответ №1:

Я помню, как где-то читал, что cpython гарантированно закрывает все дескрипторы файлов при сборе мусора, поэтому, если вы не нарушаете сегментацию, я предполагаю, что ваша программа на python не является виновником (или неправильно работает модуль C, и этот ответ бесполезен). MySQL здесь является известным нарушителем (для удаления обработчиков открытых файлов), поэтому, если задействована база данных MySQL, я бы поставил на это.

Тем не менее, вы можете использовать monkey-patch __builtin__.open , как предложил martineau, чтобы вызвать исключение, перехватить исключение и использовать модуль inspect для поиска трассировки и проверки, находится ли вызов open внутри оператора with или блока try / finally . Следующий пример очень грубый, но я надеюсь, что он поможет вам начать:

 #test.py
import foo

_old_open = open  # original function
# monkey-patch
def _new_open(*args, **kwargs):
    try:
        raise(Exception('dummy'))
    except Exception as e:
        import sys
        check_call(*sys.exc_info())
    return _old_open(*args, **kwargs)
__builtins__.open = _new_open


def check_call(e_type, e_value, tb):
    import inspect, sys
    # restore patch to avoid infinite recursion
    __builtins__.open = _old_open  
    try:
        stack = inspect.getouterframes(tb.tb_frame)
        frame_info = inspect.getframeinfo(stack[1][0])
        if frame_info.code_context[0].strip()
                     .startswith('with '):
            return
        sys.stderr.write(
           "DEBUG: open call outside with block at "
           "{f.filename}, line {f.lineno}n"
           .format(f=frame_info)
        )
    finally:
        __builtins__.open = _new_open


if __name__ == '__main__':
    foo.baz('a.txt')
    foo.bar('a.txt')

# foo.py
def bar(fname):
    f = open(fname, 'w')

def baz(fname):
    with open(fname, 'w') as f:
        f.write('dummy!')

# result:
# DEBUG: open call outside with block at 
# /path/to/foo.py, line 13
 

Комментарии:

1. Большое вам спасибо, это очень умный подход