考虑以下程序:
import tempfile
import unittest
import subprocess
def my_fn(f):
p = subprocess.Popen(['cat'], stdout=subprocess.PIPE, stdin=f)
yield p.stdout.readline()
p.kill()
p.wait()
def my_test():
with tempfile.TemporaryFile() as f:
l = list(my_fn(f))
class BuggyTestCase(unittest.TestCase):
def test_my_fn(self):
my_test()
my_test()
unittest.main()
运行它会产生以下输出:
a.py:13: ResourceWarning: unclosed file <_io.BufferedReader name=4>
l = list(my_fn(f))
ResourceWarning: Enable tracemalloc to get the object allocation traceback
.
----------------------------------------------------------------------
Ran 1 test in 0.005s
OK
警告的实际原因是什么以及如何解决?请注意,如果我注释掉 unittest.main()
问题就会消失,这意味着它特定于 subprocess+unittest+tempfile。
最佳答案
您应该关闭与您打开的 Popen()
对象关联的流。对于您的示例,这是 Popen.stdout
流,因为您指示 Popen()
对象为子进程标准输出创建管道而创建。最简单的方法是使用 Popen()
对象作为上下文管理器:
with subprocess.Popen(['cat'], stdout=subprocess.PIPE, stdin=f) as p:
yield p.stdout.readline()
p.kill()
我放弃了 p.wait()
调用,因为 Popen.__exit__()
在关闭句柄后会为您处理此问题。
如果您想进一步弄清楚导致资源警告的确切原因,那么我们可以从警告告诉我们的事情开始,enable the tracemalloc
module通过设置PYTHONTRACEMALLOC
environment variable :
$ PYTHONTRACEMALLOC=1 python a.py
a.py:13: ResourceWarning: unclosed file <_io.BufferedReader name=4>
l = list(my_fn(f))
Object allocated at (most recent call last):
File "/.../lib/python3.8/subprocess.py", lineno 844
self.stdout = io.open(c2pread, 'rb', bufsize)
.
----------------------------------------------------------------------
Ran 1 test in 0.019s
OK
因此警告是由 subprocess
模块打开的文件引发的。我在这里使用 Python 3.8.0,因此跟踪中的第 844 行指向 these lines in subprocess.py
:
if c2pread != -1:
self.stdout = io.open(c2pread, 'rb', bufsize)
c2pread
是 os.pipe()
pipe object 的一半的文件句柄创建用于处理从子进程到 Python 父进程的通信(由 Popen._get_handles()
utility method 创建,因为您设置了 stdout=PIPE
)。 io.open()
与内置的 open()
function 完全相同。 。因此,这就是创建 BufferedIOReader 实例的地方,充当管道的包装器来接收子进程产生的输出。
您还可以显式关闭p.stdout
:
p = subprocess.Popen(['cat'], stdout=subprocess.PIPE, stdin=f)
yield p.stdout.readline()
p.stdout.close()
p.kill()
p.wait()
或使用p.stdout
作为上下文管理器:
p = subprocess.Popen(['cat'], stdout=subprocess.PIPE, stdin=f)
with p.stdout:
yield p.stdout.readline()
p.kill()
p.wait()
但是始终使用subprocess.Popen()
作为上下文管理器会更容易,因为无论有多少stdin,它都会继续正常工作
、stdout
或 stderr
您创建的管道。
请注意,大多数 subprocess
代码示例不会执行此操作,因为它们倾向于使用 Popen.communicate()
,它会为您关闭文件句柄。
关于python - 资源警告 : unclosed file <_io. BufferedReader 名称=4>,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58649679/