【问题标题】:ResourceWarning unclosed socket in Python 3 Unit TestPython 3 单元测试中的 ResourceWarning 未关闭套接字
【发布时间】:2018-06-18 01:08:41
【问题描述】:

我正在修改一些代码以在 Python 2Python 3 之间兼容,但在单元测试输出中观察到警告。

/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/case.py:601:
    ResourceWarning: unclosed socket.socket fd=4,
    family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6,
    laddr=('1.1.2.3', 65087), raddr=('5.8.13.21', 8080)

一项小型研究确定,requestsboto3 等流行库也发生了这种情况。

我可以完全忽略警告或filter it。如果是我的服务,我可以在回复中设置 connection: close 标头 (link)。

这是一个在Python 3.6.1 中显示警告的示例:

app.py

import requests

class Service(object):
    def __init__(self):
        self.session = requests.Session()

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def __del__(self):
        self.session.close()

if __name__ == '__main__':
    service = Service()
    print(service.get_info())

test.py

import unittest

class TestService(unittest.TestCase):
    def test_growing(self):
        import app
        service = app.Service()
        res = service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)


if __name__ == '__main__':
    unittest.main()

是否有更好/正确的方法来管理会话,使其显式关闭,而不是依赖 __del__() 来产生这种警告。

感谢您的帮助。

【问题讨论】:

  • 为你的类创建一个close 方法,委托给底层资源的close?更好的是,让你的类实现the context manager 协议,然后将它与with 语句一起使用(__exit__ 可以调用你的close 方法,而__enter__ 可以是一个微不足道的return self,所以它是没有太多额外的工作)。
  • 不要使用 del 因为它不能保证在关闭时它会以正确的顺序被调用。这就是发明上下文管理器的原因。 @ShadowRanger 是正确的 - 添加 enterexit 方法并使用 with 语法
  • 在收到 DeprecationWarning 后出现此错误(我使用的是 assertEquals 而不是 assertEqual)。

标签: python python-3.x sockets python-unittest


【解决方案1】:

如果您不太关心警告,这是最好的解决方案

只需导入 warnings 并将这一行添加到您的驱动程序正在启动的位置 -

import warnings

warnings.filterwarnings(action="ignore", message="unclosed", category=ResourceWarning)

【讨论】:

  • 在我的情况下,在if __name__ == '__main__': 之后添加warnings.filterwarnings(... 行解决了这个问题。
  • 我不得不将“警告”行放在每个类的第一个违规函数的顶部。
  • 对我来说,它在类中有效,如果“警告”行位于文件顶部,则无效。
  • 对我来说,只有在发生问题的 read 语句之前将它放在我的 unittest 函数中才有效。
【解决方案2】:

__del__ 中包含拆卸逻辑可能会使您的程序不正确或难以推理,因为无法保证何时调用该方法,可能会导致您收到警告。有几种方法可以解决这个问题:

1) 暴露一个关闭会话的方法,并在测试中调用tearDown

unittesttearDown 方法允许您定义一些将在每次测试后运行的代码。即使测试失败或出现异常,使用此钩子关闭会话也可以工作,这很好。

app.py

import requests

class Service(object):

    def __init__(self):
        self.session = requests.Session()

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def close(self):
        self.session.close()

if __name__ == '__main__':
    service = Service()
    print(service.get_info())
    service.close()

test.py

import unittest
import app

class TestService(unittest.TestCase):

    def setUp(self):
        self.service = app.Service()
        super().setUp()

    def tearDown(self):
        self.service.close()

    def test_growing(self):
        res = self.service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)

if __name__ == '__main__':
    unittest.main()

2) 使用上下文管理器

context manager 也是一种非常有用的显式定义范围的方法。在前面的示例中,您必须确保在每个调用站点都正确调用.close(),否则您的资源将会泄漏。使用上下文管理器,即使上下文管理器范围内存在异常,也会自动处理。

在解决方案 1) 的基础上,您可以定义额外的魔法方法(__enter____exit__),以便您的类使用 with 语句。

注意:这里的好处是该代码还支持在解决方案 1) 中的用法,带有显式 .close(),如果上下文管理器由于某种原因不方便,这将很有用。

app.py

import requests

class Service(object):

    def __init__(self):
        self.session = requests.Session()

    def __enter__(self):
        return self

    def get_info(self):
        uri = 'http://api.stackexchange.com/2.2/info?site=stackoverflow'
        response = self.session.get(uri)
        if response.status_code == 200:
            return response.json()
        else:
            response.raise_for_status()

    def close(self):
        self.session.close()

    def __exit__(self, exc_type, exc_value, traceback):
        self.close()

if __name__ == '__main__':
    with Service() as service:
        print(service.get_info())

test.py

import unittest

import app

class TestService(unittest.TestCase):

    def test_growing(self):
        with app.Service() as service:
            res = service.get_info()
        self.assertTrue(res['items'][0]['new_active_users'] > 1)

if __name__ == '__main__':
    unittest.main()

根据您的需要,您可以使用其中一种,也可以同时使用setUp/tearDown 和上下文管理器,并消除该警告,并在您的代码中进行更明确的资源管理!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-27
    • 1970-01-01
    • 2011-01-27
    • 2011-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多