如何以可靠的方式跟踪 python 个对象的实例?
How to keep track of instances of python objects in a reliable way?
我希望能够跟踪几何 Point 对象的实例,以便在自动命名新名称时知道哪些名称已经 "taken"。
例如,如果创建了名为 "A"、"B" 和 "C" 的点,则下一个自动命名的点名为 "D"。如果名为 "D" 的点被删除,或者其引用丢失,则名称 "D" 将再次可用。
我的 Point
对象的主要属性被定义为属性,并且是非常标准的 x
、y
和 name
。
有问题的解决方案和 "heavy" 解决方法
我按照 here 所述进行操作,使用 weakref.WeakSet()
。我将此添加到我的 Point
class:
# class attribute
instances = weakref.WeakSet()
@classmethod
def names_in_use(cls):
return {p.name for p in Point.instances}
问题是,当我实例化一个点然后删除它时,大部分时间都是这样,但不是 总是,从 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 ==================================================================
但是,在捕获的异常中测试的代码没有创建新的 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 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
class:
def untrack(self):
Point.instances.discard(self)
并在 del myPoint
之前使用 myPoint.untrack()
(或在以其他方式失去对 Point 的引用之前)似乎可以解决问题。
但是每次都必须调用 untrack()
非常繁重...在我的测试中有很多点我需要 "untrack" 只是为了确保所有名称都可用,例如。
问题
有没有更好的方法来跟踪这些实例? (通过改进此处使用的跟踪方法,或通过任何其他更好的方法)。
不要尝试根据整个程序中存在的所有 Point
对象来跟踪可用名称。预测哪些对象将存在以及对象何时将不复存在既困难又不必要,而且在不同的 Python 实现中它的表现会非常不同。
首先,您为什么要强制实施点名称的唯一性?例如,如果您在一些 window 中绘制一个图形并且您不希望在同一图形中具有相同标签的两个点,那么让图形跟踪其中的点并拒绝一个新点取名。这也使得从图形中显式删除点或拥有两个具有独立点名称的图形变得容易。在许多其他上下文中,类似的显式容器对象可能是合理的。
如果这些是不依附于某些几何环境的自由浮点,那么为什么还要命名它们呢?如果我想表示 (3.5, 2.4) 处的一个点,我不在乎我将它命名为 A、B 还是 Bob,而且我当然不希望崩溃,因为程序中途某处的一些其他代码决定调用他们的观点鲍勃也是如此。为什么名称或名称冲突很重要?
我不知道你的用例是什么,但对于我能想象的大多数情况,最好要么只在显式容器内强制名称唯一性,要么根本不强制名称唯一性。
我希望能够跟踪几何 Point 对象的实例,以便在自动命名新名称时知道哪些名称已经 "taken"。
例如,如果创建了名为 "A"、"B" 和 "C" 的点,则下一个自动命名的点名为 "D"。如果名为 "D" 的点被删除,或者其引用丢失,则名称 "D" 将再次可用。
我的 Point
对象的主要属性被定义为属性,并且是非常标准的 x
、y
和 name
。
有问题的解决方案和 "heavy" 解决方法
我按照 here 所述进行操作,使用 weakref.WeakSet()
。我将此添加到我的 Point
class:
# class attribute
instances = weakref.WeakSet()
@classmethod
def names_in_use(cls):
return {p.name for p in Point.instances}
问题是,当我实例化一个点然后删除它时,大部分时间都是这样,但不是 总是,从 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 ==================================================================
但是,在捕获的异常中测试的代码没有创建新的 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 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
class:
def untrack(self):
Point.instances.discard(self)
并在 del myPoint
之前使用 myPoint.untrack()
(或在以其他方式失去对 Point 的引用之前)似乎可以解决问题。
但是每次都必须调用 untrack()
非常繁重...在我的测试中有很多点我需要 "untrack" 只是为了确保所有名称都可用,例如。
问题
有没有更好的方法来跟踪这些实例? (通过改进此处使用的跟踪方法,或通过任何其他更好的方法)。
不要尝试根据整个程序中存在的所有 Point
对象来跟踪可用名称。预测哪些对象将存在以及对象何时将不复存在既困难又不必要,而且在不同的 Python 实现中它的表现会非常不同。
首先,您为什么要强制实施点名称的唯一性?例如,如果您在一些 window 中绘制一个图形并且您不希望在同一图形中具有相同标签的两个点,那么让图形跟踪其中的点并拒绝一个新点取名。这也使得从图形中显式删除点或拥有两个具有独立点名称的图形变得容易。在许多其他上下文中,类似的显式容器对象可能是合理的。
如果这些是不依附于某些几何环境的自由浮点,那么为什么还要命名它们呢?如果我想表示 (3.5, 2.4) 处的一个点,我不在乎我将它命名为 A、B 还是 Bob,而且我当然不希望崩溃,因为程序中途某处的一些其他代码决定调用他们的观点鲍勃也是如此。为什么名称或名称冲突很重要?
我不知道你的用例是什么,但对于我能想象的大多数情况,最好要么只在显式容器内强制名称唯一性,要么根本不强制名称唯一性。