python程序退出前会删除对象吗_如何在Python标准库中关闭不正确的文件对象后进行清理(出现异常后)...

TL; DR:引发异常时,标准库无法关闭文件.我正在寻找处理这种情况的最佳方法.随意阅读从“仔细检查CPython的源代码”开始的段落.也向下滚动到问题的末尾,以获取一个独立的脚本,该脚本在Windows上重现此问题.

我正在编写一个Python软件包,其中使用STL的ConfigParser(2.x)或configparser(3.x)来解析用户配置文件(由于问题主要出在2.x实现中,因此我将两者都称为ConfigParser) .从现在开始,将在适当的时候链接我在GitHub上的相关代码行.当配置文件格式错误时,ConfigParser.ConfigParser.read(filenames)(在我的代码here中使用)引发ConfigParser.Error异常.我在测试套件中使用unittest.TestCase.assertRaises(ConfigParser.Error)针对这种情况设置了some code.格式错误的配置文件是带有tempfile.mkstemp的properly generated(返回的fd先由os.close关闭),而我尝试使用os.remove到remove the temp file.

os.remove是麻烦开始的地方.我的测试在Windows(同时在OS X和Ubuntu上同时运行)和Python 2.7上失败(请参见this AppVeyor build):

Traceback (most recent call last):

File "C:\projects\storyboard\tests\test_util.py", line 235, in test_option_reader

os.remove(malformed_conf_file)

WindowsError: [Error 32] The process cannot access the file because it is being used by another process: 'c:\\users\\appveyor\\appdata\\local\\temp\\1\\storyboard-test-3clysg.conf'

请注意,如上所述,malformed_conf_file由tempfile.mkstemp生成,并由os.close立即关闭,因此打开的唯一时间是当我在unittest.TestCase.assertRaises(ConfigParser.Error) context内部调用ConfigParser.ConfigParser.read([malformed_conf_file])here时.罪魁祸首似乎是STL,而不是我自己的代码.

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

for filename in filenames:

try:

fp = open(filename)

except IOError:

continue

self._read(fp, filename)

fp.close()

read_ok.append(filename)

self._read(fp,filename)引发异常(如果有),但是如您所见,如果self._read引发,则不会关闭fp,因为fp.close()仅在self之后调用. _read返回.

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

for filename in filenames:

try:

with open(filename, encoding=encoding) as fp:

self._read(fp, filename)

except OSError:

continue

read_ok.append(filename)

因此,我认为问题很明显是2.7的STL存在缺陷.而处理这种情况的最佳方法是什么?特别:

>我有什么办法可以关闭该文件?

>是否值得向bugs.python.org报告?

现在,我想我只想在os.remove上添加一个尝试..除了OSError ..(有什么建议吗?).

更新:可用于在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

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'

解决方法:

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

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

对于您的示例,另一个更有希望的修复方法是将您的except子句中的传递更改为sys.exc_clear():

try:

config.read(path)

except ConfigParser.Error:

sys.exc_clear()

这将摆脱讨厌的追溯对象,并允许关闭文件.

但是,尚不清楚在实际的应用程序中该怎么做,因为令人讨厌的except子句位于unittest内部.我认为最简单的事情可能是不直接使用assertRaises.而是编写一个辅助函数来进行测试,检查所需的异常,使用sys.exc_clear()技巧进行清理,然后引发另一个自定义异常.然后在assertRaises中包装对该辅助方法的调用.这样,您就可以控制ConfigParser引发的有问题的异常,并可以正确清除它(哪个单元测试没有执行).

这是我的意思的草图:

# 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!")

当然,我实际上并未对此进行测试,因为我没有在您的代码中设置整个unittest.您可能需要稍微调整一下. (想一想,如果执行此操作,您甚至可能甚至不需要exc_clear(),因为既然异常处理程序现在位于单独的函数中,则应该在helperMethod退出时正确清除回溯.)但是,我认为这种想法可能带你去某个地方.基本上,您需要确保捕获到了此特定ConfigParser.Error的except子句是您编写的,以便可以在尝试删除测试文件之前对其进行清理.

附录:似乎如果上下文管理器处理异常,则回溯实际上将被存储,直到包含with块的函数结束为止,如以下示例所示:

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())

即使with块在打印发生时已经结束,所以应该完成异常处理,但是sys.exc_info仍会返回异常信息,就好像存在活动异常一样.这也是代码中发生的事情:with assertRaises块导致回溯一直持续到该函数的结尾,从而干扰了os.remove.这似乎是错误的行为,并且我注意到它在Python 3中不再能以这种方式工作(打印输出(无,无,无)),因此我想这是在Python 3中修复的疣.

基于此,我怀疑仅在os.remove之前(在with assertRaises块之后)插入sys.exc_clear()就足够了.

标签:python-2-7,exception-handling,python

来源: https://codeday.me/bug/20191120/2043832.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值