繁体   English   中英

ResourceWarning:未关闭的文件 <_io.BufferedReader name=4>

[英]ResourceWarning: unclosed file <_io.BufferedReader name=4>

考虑以下程序:

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

运行它会产生以下 output:

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() object 关联的流。 For your example, that's the Popen.stdout stream, created because you instructed the Popen Popen() object to create a pipe for the child process standard output. 最简单的方法是使用 Popen Popen() object 作为上下文管理器:

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

在关闭句柄后,我放弃了p.wait()调用,因为 Popen Popen.__exit__()为您处理了这个问题。

如果您想进一步找出导致资源警告的确切原因,那么我们可以从警告告诉我们的内容开始,并通过设置PYTHONTRACEMALLOC环境变量启用tracemalloc模块

$ 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 行指向 subprocess.py 中的subprocess.py

if c2pread != -1:
    self.stdout = io.open(c2pread, 'rb', bufsize)

c2pread is the file handle for one half of a os.pipe() pipe object created to handle communication from child process to Python parent process (created by the Popen._get_handles() utility method , because you set stdout=PIPE ). io.open()与内置的open() function 完全相同 所以这是创建BufferedIOReader实例的地方,它充当 pipe 的包装器,以接收子进程生成的 output。

您还可以显式关闭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() ,它会为您关闭文件句柄。

运行单元测试时,我还收到了一些ResourceWarning: unclosed file <_io.FileIO name=7 mode='wb' closefd=True>消息。 我不能使用Popen object 作为上下文管理器,所以我在我的类__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()

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

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

它以如下方式解决了这个问题:

def my_test():
    with tempfile.TemporaryFile() as f:
        x = my_fn(f)
        l = list()
        l.append(x)
        # print(l)

或者如下方式:

def my_test():
    with tempfile.TemporaryFile() as f:
        l = list([my_fn(f)])

以下对这个问题有帮助:

list Found at: builtins
list() -> new empty list
    list(iterable) -> new list initialized from iterable's 
     items

https://docs.python.org/3/library/stdtypes.html#list提到了以下内容:

 class list([iterable])

    Lists may be constructed in several ways:

        * Using a pair of square brackets to denote the empty list: []
        * Using square brackets, separating items with commas: [a], [a, b, c]
        * Using a list comprehension: [x for x in iterable]
        * Using the type constructor: list() or list(iterable)

    The constructor builds a list whose items are the same and in the same order as
iterable’s items. iterable may be either a sequence, a container that supports 
iteration, or an iterator object. If iterable is already a list, a copy is made and 
returned, similar to iterable[:]. For example, list('abc') returns ['a', 'b', 'c']
and list( (1, 2, 3) ) returns [1, 2, 3]. If no argument is given, the constructor 
creates a new empty list, [].

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM