"t = foo(); with t" 与 "with foo()" 有何不同?

How can "t = foo(); with t" be different from "with foo()"?

我正在测试一个程序转换工具。它导致 CPython 测试之一失败,但我终究无法弄清楚原因。

这是 test_sax.py 中来自 CPython 测试套件 https://github.com/python/cpython/blob/master/Lib/test/test_sax.py#L143 的测试之一的缩小版本。此代码段写入一个 XML 文件,然后以错误的编码将其读回。这会导致 xml.sax.parse 失败并引发异常。当它这样做时,它打开的文件应该会泄露。垃圾收集器检测到此泄漏。以下测试检查此行为:

def test_parse_bytes(self):
    make_xml_file(self.data, 'iso-8859-1', None)
    with support.check_warnings(('unclosed file', ResourceWarning)) :
        with self.assertRaises(SAXException):
            self.check_parse(TESTFN)
        gc.collect()

我的程序转换工具将此代码段更改为如下所示:

def test_parse_bytes(self):
    make_xml_file(self.data, 'iso-8859-1', None)
    with support.check_warnings(('unclosed file', ResourceWarning)) :
        t = self.assertRaises(SAXException)
        with t:
            self.check_parse(TESTFN)
        gc.collect()

据我所知 Python,这些片段也应该是完全相同的。当我在调试器中单步执行时,我无法分辨他们在做什么。然而,不知何故,后一个片段导致文件没有泄漏,从而导致测试失败。为什么?

在第一个片段中,self.assertRaises(SAXException) 的结果在 gc.collect() 被调用时超出了范围,因此它是垃圾并且 gua运行teed 被收集;在第二个片段中,引用 t 仍在范围内,所以它没有。有一些从 t 到文件句柄的引用路径——也许它包含对引发的异常的引用,而异常包含对文件的引用。因此,调用 gc.collect() 时文件不是垃圾,因此不会自动关闭。外部 with 块然后检查未关闭的文件,并找到一个文件,虽然它是垃圾并且如果垃圾收集器再次 运行 并且 t 超出范围将被关闭,但是仍然是仍然开放。