Skip to main content

Python 的另一个垃圾对象跟踪器

项目描述

Pypi 上的版本 Travis 测试状态(主) Appveyor 测试状态(主) 文档构建状态(主) 测试覆盖率(主)

概述

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下提供的。

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

yagot-0.5.0.tar.gz (19.5 kB 查看哈希

已上传 source

内置分布

yagot-0.5.0-py2.py3-none-any.whl (16.9 kB 查看哈希

已上传 py2 py3