Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/python-2.7/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在Python标准库中错误关闭文件对象后进行清理(发生异常后)_Python_Python 2.7_Exception Handling - Fatal编程技术网

如何在Python标准库中错误关闭文件对象后进行清理(发生异常后)

如何在Python标准库中错误关闭文件对象后进行清理(发生异常后),python,python-2.7,exception-handling,Python,Python 2.7,Exception Handling,TL;DR:引发异常时,标准库无法关闭文件。我在寻找处理这种情况的最佳方法。请随意阅读以“仔细检查CPython的源代码”开头的段落。也可以向下滚动到问题的末尾,获取一个在Windows上复制此问题的自包含脚本 我正在编写一个Python包,其中我使用STL的ConfigParser(2.x)或ConfigParser(3.x)来解析用户配置文件(我将两者都称为ConfigParser,因为问题主要在于2.x实现)。从现在起,我在GitHub上的相关代码行将在适当的时候进行链接ConfigPar

TL;DR:引发异常时,标准库无法关闭文件。我在寻找处理这种情况的最佳方法。请随意阅读以“仔细检查CPython的源代码”开头的段落。也可以向下滚动到问题的末尾,获取一个在Windows上复制此问题的自包含脚本

我正在编写一个Python包,其中我使用STL的
ConfigParser
(2.x)或
ConfigParser
(3.x)来解析用户配置文件(我将两者都称为
ConfigParser
,因为问题主要在于2.x实现)。从现在起,我在GitHub上的相关代码行将在适当的时候进行链接
ConfigParser.ConfigParser.read(文件名)
(在我的代码中使用)在配置文件格式错误时引发
ConfigParser.Error
异常。我在测试套件中使用
unittest.TestCase.assertRaises(ConfigParser.Error)
针对这种情况。格式错误的配置文件使用
tempfile.mkstemp
(返回的fd在任何操作之前使用
os.close
关闭),我尝试使用
os.remove

os.remove
是故障开始的地方。我的测试在使用Python 2.7的Windows(同时使用OS X和Ubuntu)上失败(请参阅):

请注意,正如我上面所说的,
格式错误的配置文件
是使用
tempfile.mkstemp
生成的,并立即使用
os.close
关闭,因此它打开的唯一时间是在。因此,罪魁祸首似乎是STL,而不是我自己的代码

仔细检查CPython的源代码后,我发现当引发异常时,
ConfigParser.ConfigPaser.read
确实无法正确关闭文件。2.7()中的
read
方法有以下几行:

对于文件名中的文件名:
尝试:
fp=打开(文件名)
除IOError外:
持续
自读(fp,文件名)
fp.close()
read_ok.append(文件名)
异常(如果有)是由
self.\u read(fp,filename)
引发的,但正如您所看到的,如果
self.\u read
引发,则不会关闭
fp
,因为
fp.close()
仅在
self.\u read
返回后调用

同时,3.4()中的
read
方法不会遇到同样的问题,因为这一次它们在上下文中正确地嵌入了文件处理:

对于文件名中的文件名:
尝试:
将open(filename,encoding=encoding)作为fp:
自读(fp,文件名)
除操作错误外:
持续
read_ok.append(文件名)
所以我认为问题很明显是2.7的STL中的一个缺陷。处理这种情况的最佳方法是什么?
具体来说:

  • 我这边能做些什么来关闭那个文件吗
  • 值得向bugs.python.org报告吗
现在我想我只需要添加一个
试试。。除了操作系统错误。
到该操作系统。删除()(有什么建议吗?)


更新:一个自包含脚本,可用于在Windows上重现此问题:

#!/usr/bin/env python2.7
import ConfigParser
import os
import tempfile

def main():
    fd, path = tempfile.mkstemp()
    os.close(fd)
    with open(path, 'w') as f:
        f.write("malformed\n")
    config = ConfigParser.ConfigParser()
    try:
        config.read(path)
    except ConfigParser.Error:
        pass
    os.remove(path)

if __name__ == '__main__':
    main()
当我使用Python 2.7解释器运行它时:

Traceback (most recent call last):
  File ".\example.py", line 19, in <module>
    main()
  File ".\example.py", line 16, in main
    os.remove(path)
WindowsError: [Error 32] The process cannot access the file because it is being used by another process: 'c:\\users\\redacted\\appdata\\local\\temp\\tmp07yq2v'
回溯(最近一次呼叫最后一次):
文件“\example.py”,第19行,在
main()
文件“\example.py”,第16行,主
删除操作系统(路径)
WindowsError:[错误32]进程无法访问该文件,因为另一个进程正在使用它:“c:\\users\\Redact\\appdata\\local\\temp\\tmp07yq2v”

这是一个有趣的问题。正如Lukas Graf在一篇评论中指出的,问题似乎在于异常回溯对象持有对引发异常的调用帧的引用。此调用框架包括当时存在的局部变量,其中一个是对打开文件的引用。因此该文件对象仍有对它的引用,并且未正确关闭

对于您的自包含示例,只需删除
try/except ConfigParser.Error
“works”:有关格式错误的配置文件的异常未被捕获并停止程序。但是,在实际应用程序中,
assertRaises
正在捕获异常,以便检查它是否是您正在测试的异常。我不能100%确定为什么即使在使用assertRaises执行
块之后,回溯仍然存在,但显然它确实存在

例如,另一个更有希望的修复方法是将
except
子句中的
pass
更改为
sys.exc_clear()

这将消除讨厌的回溯对象,并允许关闭文件

然而,在实际的应用程序中如何做到这一点还不清楚,因为在
unittest
中有一个令人不快的
except
子句。我认为最简单的事情可能是不要直接使用
assertRaises
。相反,编写一个帮助函数来执行测试,检查所需的异常,使用
sys.exc_clear()
技巧进行清理,然后引发另一个自定义异常。然后在
assertRaises
中包装对该助手方法的调用。通过这种方式,您可以控制ConfigParser引发的有问题的异常,并可以正确地清理它(这是unittest所没有做的)

这是我的意思的草图:

# in your test method
assertRaises(CleanedUpConfigError, helperMethod, conf_file, malformed_conf_file)

# helper method that you add to your test case class
def helperMethod(self, conf_file, malformed_conf_file):
     gotRightError = False
     try:
          or6 = OptionReader(
               config_files=[conf_file, malformed_conf_file],
               section='sec',
          )
     except ConfigParser.Error:
          gotRightError = True
          sys.exc_clear()
     if gotRightError:
          raise CleanedUpConfigError("Config error was raised and cleaned up!")
当然,我还没有实际测试这个,因为我没有用您的代码设置整个单元测试。你可能需要稍微调整一下。(想一想,如果这样做,您甚至可能不需要
exc_clear()
,因为异常处理程序现在位于一个单独的函数中,所以当
helperMethod
退出时,应该正确地清除回溯。)然而,我认为这个想法可能会让您有所收获。
try:
    config.read(path)
except ConfigParser.Error:
    sys.exc_clear()
# in your test method
assertRaises(CleanedUpConfigError, helperMethod, conf_file, malformed_conf_file)

# helper method that you add to your test case class
def helperMethod(self, conf_file, malformed_conf_file):
     gotRightError = False
     try:
          or6 = OptionReader(
               config_files=[conf_file, malformed_conf_file],
               section='sec',
          )
     except ConfigParser.Error:
          gotRightError = True
          sys.exc_clear()
     if gotRightError:
          raise CleanedUpConfigError("Config error was raised and cleaned up!")
class CM(object):
    def __init__(self):
        pass

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        return True

def foo():
    with CM():
        raise ValueError
    print(sys.exc_info())