【问题标题】:ResourceWarning: unclosed file <_io.BufferedReader name=4>ResourceWarning:未关闭的文件 <_io.BufferedReader name=4>
【发布时间】:2022-04-13 17:44:35
【问题描述】:

考虑以下程序:

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。

【问题讨论】:

标签: python


【解决方案1】:

您应该关闭与您打开的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 是为处理从子进程到 Python 父进程的通信而创建的 os.pipe() pipe object 的一半的文件句柄(由 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() 作为上下文管理器更容易,因为无论有多少stdinstdoutstderr 管道,它都会继续正常工作已创建。

请注意,大多数subprocess 代码示例不这样做,因为它们倾向于使用Popen.communicate(),它会为您关闭文件句柄。

【讨论】:

    【解决方案2】:

    在运行单元测试时,我还收到了一些 ResourceWarning: unclosed file &lt;_io.FileIO name=7 mode='wb' closefd=True&gt; 消息。我不能使用Popen 对象作为上下文管理器,所以我在我的类__del__ 方法中这样做了:

    
    import subprocess
    
    class MyClass:
        def __init__(self):
            self.child= subprocess.Popen(['dir'],
                stdout=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stderr=subprocess.PIPE)
    
        def __del__(self):
            self.child.terminate()
            self.child.communicate()
    

    【讨论】:

      【解决方案3】:

      您只需将my_fn 更改为使用when,就不需要killwait。 Python 会为你关闭它:

      def my_fn(f):
          with subprocess.Popen(['cat'], stdout=subprocess.PIPE, stdin=f) as p:
              yield p.stdout.readline()
      
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-08-18
        • 2020-08-06
        • 1970-01-01
        • 2020-06-25
        • 2018-06-18
        • 2018-11-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多