如何以可靠的方式跟踪python对象的实例? [英] How to keep track of instances of python objects in a reliable way?

查看:63
本文介绍了如何以可靠的方式跟踪python对象的实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我希望能够跟踪几何 Point 对象的实例,以便在自动命名新名称时知道已经采用"了哪些名称.

I would like to be able to keep track of instances of geometric Point objects in order to know what names are already "taken" when automatically naming a new one.

例如,如果已创建名为A"、B"和C"的点,则下一个自动命名的点将命名为D".如果名为D"的点被删除,或者其引用丢失,则名称D"将再次可用.

For instance, if Points named "A", "B" and "C" have been created, then the next automatically named Point is named "D". If Point named "D" gets deleted, or its reference gets lost, then name "D" becomes available again.

我的Point 对象的主要属性被定义为属性并且是非常标准的xyname.

The main attributes of my Point objects are defined as properties and are the quite standard x, y and name.

我按照此处的描述进行,使用weakref.WeakSet().我将此添加到我的 Point 类中:

I proceeded as described here, using a weakref.WeakSet(). I added this to my Point class:

# 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 -rw),然后 如果在测试中引发某个异常,那么实例 never 被删除(可能的解释如下).

Problem is, when I instanciate a Point and then delete it, it is most of the time, but not always, removed from Point.instances. I noticed that, if I run the tests suite (pytest -x -vv -r w), then if a certain exception is raised in the test, then the instance never gets deleted (probable explanation to be read somewhat below).

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

In the following test code, after the first deletion of p, it always gets removed from Point.instances, but after the second deletion of p, it never gets deleted (test results are always the same) and the last assert statement fails:

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 ==================================================================

然而,在捕获的异常中测试的代码并没有创建新的 Point 实例:

Yet, the code tested in the catched exception does not create a new Point instance:

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 基本上包含数字.

where _x and _y basically contain numbers.

原因似乎是(引用自 python 的文档):

The reason seems to be (quoting from python's doc):

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

CPython implementation detail: It is possible for a reference cycle to prevent the reference count of an object from going to zero. In this case, the cycle will be later detected and deleted by the cyclic garbage collector. A common cause of reference cycles is when an exception has been caught in a local variable.

解决方法

将此方法添加到Point类:

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

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

and using myPoint.untrack() before del myPoint (or before losing reference to the Point in another way) seems to solve the problem.

但是每次都必须调用 untrack() 非常繁重......在我的测试中,有很多点我需要取消跟踪"以确保所有名称都可用,例如.

But this is quite heavy to have to call untrack() each time... in my tests there are a lot of Points I will need to "untrack" only to ensure all names are available, for instance.

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

Is there any better way to keep track of these instances? (either by improving the tracking method used here, or by any other better mean).

推荐答案

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

Don't try to track available names based on all Point objects that exist in the entire program. Predicting what objects will exist and when objects will cease to exist is difficult and unnecessary, and it will behave very differently on different Python implementations.

首先,您为什么要强制执行点名称的唯一性?例如,如果您在某个窗口中绘制图形,并且您不希望在同一图形中绘制两个具有相同标签的点,则让图形跟踪其中的点并拒绝具有已取名称的新点.这也使得从图形中明确删除点变得容易,或者有两个具有独立点名称的图形.在许多其他上下文中,类似的显式容器对象可能是合理的.

First, why are you trying to enforce Point name uniqueness at all? If, for example, you're drawing a figure in some window and you don't want two points with the same label in the same figure, then have the figure track the points in it and reject a new point with a taken name. This also makes it easy to explicitly remove points from a figure, or have two figures with independent point names. There are a number of other contexts where a similar explicit container object may be reasonable.

如果这些是不附加到某些几何环境的自由浮动点,那么为什么要命名它们呢?如果我想表示 (3.5, 2.4) 处的一个点,我不在乎我将它命名为 A 还是 B 或 Bob,而且我当然不希望崩溃,因为程序中途某个地方的其他一些代码决定调用他们的观点鲍勃也是.为什么名称或名称冲突很重要?

If these are free-floating points not attached to some geometry environment, then why name them at all? If I want to represent a point at (3.5, 2.4), I don't care whether I name it A or B or Bob, and I certainly don't want a crash because some other code somewhere halfway across the program decided to call their point Bob too. Why do names or name collisions matter?

我不知道您的用例是什么,但对于我能想象的大多数情况,最好要么只在显式容器内强制执行名称唯一性,要么根本不强制执行名称唯一性.

I don't know what your use case is, but for most I can imagine, it'd be best to either only enforce name uniqueness within an explicit container, or not enforce name uniqueness at all.

这篇关于如何以可靠的方式跟踪python对象的实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆