Python 的另一个垃圾对象跟踪器
项目描述
概述
Yagot(Yet Another Garbage Object Tracker)是 Python 开发人员帮助查找垃圾收集和内存泄漏问题的工具:
它可以确定由函数或方法引起的收集对象的集合。
收集的对象是 Python 在它们变得无法访问时无法立即释放的对象,最终由 Python 垃圾收集器释放。这通常是由于要释放的对象所涉及的循环引用的存在引起的。垃圾收集器旨在在释放对象时处理循环引用。
收集的对象本身不是问题,但它们会导致大量内存使用并且通常可以被消除。
它可以确定由函数或方法引起的无法收集的对象集。
不可回收的对象是 Python 在垃圾回收期间无法释放的对象,即使在运行完整的回收时(即在所有代的 Python 分代垃圾回收器上)。
不可回收的对象仍然分配在最后一代的垃圾收集器中。在其最后一代的每次运行中,垃圾收集器都会尝试释放这些对象。这些不断的尝试最终成功似乎很少见,因此这些对象基本上可以认为是内存泄漏。
有关Python 中对象释放的更详细说明,请参阅 背景部分。
Yagot 很容易通过以下任一方式使用:
它提供了一个名为yagot的pytest插件,用于检测由测试用例引起的已收集和不可收集的对象。通过指定命令行选项或环境变量启用此检测,不需要修改测试用例。
它提供了一个名为garbage_checked的Python 装饰器 ,用于检测由装饰函数或方法引起的已收集和不可收集的对象。这允许使用 Yagot 独立于任何测试框架或与其他测试框架,如鼻子或unittest。
Yagot 适用于 Python 的正常(非调试)版本。
安装
要将 yagot 包的最新发布版本安装到您的活动 Python 环境中:
$ pip install yagot
这还将安装任何必备的 Python 包。
有关更多详细信息和替代安装方式,请参阅安装。
用法
下面是一个示例,说明如何使用 Yagot 使用 yagot pytest 插件提供的命令行选项检测由 pytest 测试用例引起的收集对象:
$ cat examples/test_1.py
def test_selfref_dict():
d1 = dict()
d1['self'] = d1
$ pytest examples --yagot -k test_1.py
===================================== test session starts ======================================
platform darwin -- Python 3.7.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /Users/maiera/PycharmProjects/yagot/python-yagot
plugins: cov-2.8.1, yagot-0.1.0.dev1
yagot: Checking for collected and uncollectable objects, ignoring types: (none)
collected 2 items / 1 deselected / 1 selected
examples/test_1.py .E [100%]
============================================ ERRORS ============================================
____________________________ ERROR at teardown of test_selfref_dict ____________________________
item = <Function test_selfref_dict>
def pytest_runtest_teardown(item):
"""
py.test hook that is called when tearing down a test item.
We use this hook to stop tracking and check the track result.
"""
config = item.config
enabled = config.getvalue('yagot')
if enabled:
import yagot
tracker = yagot.GarbageTracker.get_tracker()
tracker.stop()
location = "{file}::{func}". \
format(file=item.location[0], func=item.name)
> assert not tracker.garbage, tracker.assert_message(location)
E AssertionError:
E There were 1 collected or uncollectable object(s) caused by function examples/test_1.py::test_selfref_dict:
E
E 1: <class 'dict'> object at 0x10df6ceb0:
E {'self': <Recursive reference to dict object at 0x10df6ceb0>}
E
E assert not [{'self': {'self': {'self': {'self': {'self': {...}}}}}}]
E + where [{'self': {'self': {'self': {'self': {'self': {...}}}}}}] = <yagot._garbagetracker.GarbageTracker object at 0x10df15f10>.garbage
yagot_pytest/plugin.py:148: AssertionError
=========================== 1 passed, 1 deselected, 1 error in 0.07s ===========================
下面是一个示例,说明如何使用 Yagot 来检测由函数使用垃圾检查装饰器引起的收集 对象 。在这个例子中加载了 yagot pytest 插件,它的存在由 pytest 报告,但它没有被使用:
$ cat examples/test_2.py
import yagot
@yagot.garbage_checked()
def test_selfref_dict():
d1 = dict()
d1['self'] = d1
$ pytest examples -k test_2.py
===================================== test session starts ======================================
platform darwin -- Python 3.7.5, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /Users/maiera/PycharmProjects/yagot/python-yagot
plugins: cov-2.8.1, yagot-0.1.0.dev1
collected 2 items / 1 deselected / 1 selected
examples/test_2.py F [100%]
=========================================== FAILURES ===========================================
______________________________________ test_selfref_dict _______________________________________
args = (), kwargs = {}, tracker = <yagot._garbagetracker.GarbageTracker object at 0x1078853d0>
ret = None, location = 'test_2::test_selfref_dict'
@py_assert1 = [{'self': {'self': {'self': {'self': {'self': {...}}}}}}], @py_assert3 = False
@py_format4 = "\n~There were 1 collected or uncollectable object(s) caused by function test_2::test_selfref_dict:\n~\n~1: <class 'di...elf': {'self': {'self': {'self': {...}}}}}}] = <yagot._garbagetracker.GarbageTracker object at 0x1078853d0>.garbage\n}"
@functools.wraps(func)
def wrapper_garbage_checked(*args, **kwargs):
"Wrapper function for the garbage_checked decorator"
tracker = GarbageTracker.get_tracker()
tracker.enable(leaks_only=leaks_only)
tracker.start()
tracker.ignore_types(type_list=ignore_types)
ret = func(*args, **kwargs) # The decorated function
tracker.stop()
location = "{module}::{function}".format(
module=func.__module__, function=func.__name__)
> assert not tracker.garbage, tracker.assert_message(location)
E AssertionError:
E There were 1 collected or uncollectable object(s) caused by function test_2::test_selfref_dict:
E
E 1: <class 'dict'> object at 0x1078843c0:
E {'self': <Recursive reference to dict object at 0x1078843c0>}
E
E assert not [{'self': {'self': {'self': {'self': {'self': {...}}}}}}]
E + where [{'self': {'self': {'self': {'self': {'self': {...}}}}}}] = <yagot._garbagetracker.GarbageTracker object at 0x1078853d0>.garbage
yagot/_decorators.py:67: AssertionError
=============================== 1 failed, 1 deselected in 0.07s ================================
在这两种用法中,Yagot 都会报告有一个由测试功能引起的已收集或无法收集的对象。断言消息提供有关该对象的一些详细信息。在这种情况下,我们可以看到该对象是一个dict对象,并且它的“self”项引用回同一个dict对象,因此存在循环引用导致该对象成为可收集对象。
该循环引用非常简单,Python 垃圾收集器可以将其分解,因此该对象不会变得无法收集。
pytest 显示的故障位置和源代码是garbage_checked装饰器和pytest_runtest_teardown函数的包装函数,因为这是检测到它的地方。导致创建对象的修饰函数或 pytest 测试用例使用“module::function”表示法在断言消息中报告。
了解导致对象变为可收集对象的测试函数test_selfref_dict()是识别问题代码的良好开端,并且在我们的示例案例中很容易做到,因为测试函数足够简单。如果测试函数过于复杂而无法识别罪魁祸首,则可以将其拆分为多个更简单的测试函数,或者可以添加新的测试函数来检查所使用的特定类型的对象。
作为练习,通过创建空字典来测试标准dict类和 collections.OrderedDict类。您会发现在 CPython 2.7 上,collections.OrderedDict会导致收集对象(请参阅 issue9825)。
garbage_checked装饰器可以以任何顺序与任何其他装饰器组合。请注意,它始终跟踪下一个内部函数,因此除非您想跟踪其他装饰器创建的垃圾,否则您希望将其直接放在测试函数上,作为最内部的装饰器,如下例所示:
import pytest
import yagot
@pytest.mark.parametrize('parm2', [ ... ])
@pytest.mark.parametrize('parm1', [ ... ])
@yagot.garbage_checked()
def test_something(parm1, parm2):
pass # some test code
文档
更改历史
贡献
有关如何为 Yagot 项目做出贡献的信息,请参阅 贡献。
执照
Yagot 项目是在 Apache Software License 2.0下提供的。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。