有没有一种简单的方法可以找到代码的哪个部分没有关闭文件

时间:2014-06-26 14:14:23

标签: python file

我有一个包含大量代码的大型程序。它正在打开文件,但没有关闭它。

问题:

有没有一种简单的方法可以找出这种情况发生在哪里?

更多详情:

操作系统 - 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 个答案:

答案 0 :(得分:3)

我记得在某处查看cpython保证在垃圾收集时关闭所有文件句柄,所以除非你是segfaulting我猜你的python程序不是罪魁祸首(或者有一个C模块行为不端,这个答案毫无价值)。 MySQL在这里是一个已知的攻击者(用于删除打开的文件处理程序),所以如果涉及到MySQL数据库,我会打赌它。

那就是说,你可以按照martineau的建议来修补__builtin__.open以引发异常,捕获异常并使用inspect模块搜索回溯并检查open调用是否在with语句中或尝试/ 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