如何以可靠的方式跟踪python对象的实例?

时间:2018-01-30 19:01:05

标签: python python-3.x pytest weak-references

我希望能够跟踪几何Point对象的实例,以便了解已经采用的名称""当自动命名一个新的。

例如,如果点名为" A"," B"和" C"已创建,然后下一个自动命名的Point命名为" D"。如果Point命名为" D"被删除,或其引用丢失,然后命名" D"再次可用。

我的Point个对象的主要属性被定义为属性,非常标准xyname

有问题的解决方案和"重"解决方法

我使用weakref.WeakSet()按照here所述进行了操作。我将此添加到我的Point课程中:

# class attribute
instances = weakref.WeakSet()

@classmethod
def names_in_use(cls):
    return {p.name for p in Point.instances}

问题是,当我实例化一个Point然后将其删除时,大部分时间,但不是总是,从Point.instances中移除。我注意到,如果我运行测试套件(pytest -x -vv -r w),那么如果在测试中引发了某个异常,则实例永远不会被删除(可能的解释有点在下面阅读。)

在以下测试代码中,首次删除p后,它始终会从Point.instances中删除,但在第二次删除p后,它永远不会被删除(测试结果)总是相同的)并且最后一个assert语句失败:

def test_instances():
    import sys
    p = Point(0, 0, 'A')
    del p
    sys.stderr.write('1 - Point.instances={}\n'.format(Point.instances))
    assert len(Point.instances) == 0
    assert Point.names_in_use() == set()
    p = Point(0, 0, 'A')
    with pytest.raises(TypeError) as excinfo:
        p.same_as('B')
    assert str(excinfo.value) == 'Can only test if another Point is at the ' \
        'same place. Got a <class \'str\'> instead.'
    del p
    sys.stderr.write('2 - Point.instances={}\n'.format(Point.instances))
    assert len(Point.instances) == 0

结果如下:

tests/04_geometry/01_point_test.py::test_instances FAILED

=============================================================================== FAILURES ===============================================================================
____________________________________________________________________________ test_instances ____________________________________________________________________________

    def test_instances():
        import sys
        p = Point(0, 0, 'A')
        del p
        sys.stderr.write('1 - Point.instances={}\n'.format(Point.instances))
        assert len(Point.instances) == 0
        assert Point.names_in_use() == set()
        p = Point(0, 0, 'A')
        with pytest.raises(TypeError) as excinfo:
            p.same_as('B')
        assert str(excinfo.value) == 'Can only test if another Point is at the ' \
            'same place. Got a <class \'str\'> instead.'
        del p
        sys.stderr.write('2 - Point.instances={}\n'.format(Point.instances))
>       assert len(Point.instances) == 0
E       assert 1 == 0
E        +  where 1 = len(<_weakrefset.WeakSet object at 0x7ffb986a5048>)
E        +    where <_weakrefset.WeakSet object at 0x7ffb986a5048> = Point.instances

tests/04_geometry/01_point_test.py:42: AssertionError
------------------------------------------------------------------------- Captured stderr call -------------------------------------------------------------------------
1 - Point.instances=<_weakrefset.WeakSet object at 0x7ffb986a5048>
2 - Point.instances=<_weakrefset.WeakSet object at 0x7ffb986a5048>
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
================================================================= 1 failed, 82 passed in 0.36 seconds ==================================================================

然而,在catched异常中测试的代码不会创建新的Point实例:

def same_as(self, other):
    """Test geometric equality."""
    if not isinstance(other, Point):
        raise TypeError('Can only test if another Point is at the same '
                        'place. Got a {} instead.'.format(type(other)))
    return self.coordinates == other.coordinates

和坐标基本上是:

@property
def coordinates(self):
    return (self._x, self._y)

其中_x_y基本上包含数字。

原因似乎是(引自python's doc):

  

CPython实现细节:参考周期可以防止对象的引用计数变为零。在这种情况下,循环垃圾收集器稍后将检测并删除该循环。引用周期的常见原因是在局部变量中捕获到异常。

解决方法

将此方法添加到Point类:

def untrack(self):
    Point.instances.discard(self)

并在myPoint.untrack()之前使用del myPoint(或在以另一种方式失去对Point的引用之前)似乎解决了这个问题。

但是每次打电话给untrack()都很重要......在我的测试中,我需要很多积分,然后才能解决问题。例如,仅确保所有名称都可用。

问题

有没有更好的方法来跟踪这些实例? (通过改进这里使用的跟踪方法,或通过任何其他更好的方法)。

1 个答案:

答案 0 :(得分:7)

请勿尝试根据整个程序中存在的所有Point对象来跟踪可用名称。预测哪些对象将存在以及何时对象将不再存在是困难和不必要的,并且在不同的Python实现上它将表现得非常不同。

首先,为什么要尝试强制执行Point name唯一性?例如,如果您在某个窗口中绘制了一个图形并且您不想在同一个图形中使用相同标签的两个点,那么让图形跟踪其中的点并拒绝新的点一个名字。这也可以很容易地从图中明确地删除点,或者具有两个具有独立点名的图。还有许多其他上下文,其中类似的显式容器对象可能是合理的。

如果这些是无法附加到某些几何环境的自由浮点,那么为什么要将它们命名呢?如果我想代表一点(3.5,2.4),我不在乎我是否将它命名为A或B或Bob,而且我当然不想要崩溃,因为其他代码在某个地方的中途节目决定也给鲍勃打电话。为什么名称或名称冲突很重要?

我不知道您的用例是什么,但对于大多数我可以想象,最好只在显式容器中强制执行名称唯一性,或者根本不强制执行名称唯一性。< / p>