英文:
Why is `__del__` method not called if an instance is stored?
问题
为了加深我对装饰器的理解,我尝试提出了一个替代方案,用于 Tonie Victor 的博客 中的 count_instances
装饰器(在 类装饰器的常见用例 部分)。
与其使用函数,我使用一个类作为装饰器,并扩展了功能,以便在删除装饰类的实例时保持正确的计数。为此,我装饰了装饰类的 __del__
方法。
实际上,在我的简单测试案例中,这确实按预期工作。然而,当我尝试将装饰器从一个实例 counter 更改为包含所有实例的实例 tracker 时,它突然不再按预期工作。
更具体地说,如果我将装饰类的实例添加到 InstanceCounter
类中的列表中,那么如果删除实例,(装饰的)__del__
方法将不再被调用,计数也将不正确。
请注意,我尚未为 tracker 实现所有功能。我计划:
-
用一个返回
_instances
长度的属性来替换_count
属性; -
在
wrapper
中添加self._instances.pop(self._instances.index(obj))
。
class InstanceCounter:
def __init__(self, cls):
self._instances = []
self._count = 0
if hasattr(cls, "__del__"):
del_meth = cls.__del__
else:
del_meth = None
cls.__del__ = self._del_decorator(del_meth)
self._wrapped_cls = cls
@property
def instance_count(self):
return self._count
def __call__(self):
self._count += 1
obj = self._wrapped_cls()
# self._instances.append(obj) # 当取消注释此行时...
return obj
def _del_decorator(self, del_method):
def wrapper(obj):
self._count -= 1
if del_method:
del_method(obj)
return wrapper
@InstanceCounter
class MyClass:
def __init__(self):
pass
def __del__(self): # ... 这个方法将不会被执行。
print(f"Deleting object of class {__class__}")
@InstanceCounter
class OtherClass:
def __init__(self):
pass
# 带有预期输出的测试案例。
my_a = MyClass()
print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
my_b = MyClass()
print(f"{MyClass.instance_count=}") # MyClass.instance_count=2
del my_a
print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
oth_c = OtherClass()
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
oth_d = OtherClass()
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=2
del oth_c
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
del oth_d
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=0
为什么如果将类的实例保存在装饰类中,__del__
方法不会被调用?如何修复这个问题?
英文:
To deepen my understanding of decorators I tried to come up with an alternative solution for the count_instances
decorator in Tonie Victor's blog (section Common Use Cases of Class Decorators).
Instead of using a function, I use a class as a decorator and I extend the functionality to keep a proper count even when instances of the decorated class are deleted. For that I decorate the __del__
method of the decorated class.
In fact this works as expected on my simple test cases. However, when I try to change the decorator from an instance counter to an instance tracker that contains a list of all instances, it all of a sudden does not work as expected anymore.
More specifically, if I add the instance of the decorated class to a list in the InstanceCounter
class, the (decorated) __del__
method isn't called anymore if I delete an instance and the counts are wrong.
Note that I have not implemented all functionality for the tracker yet. I planned to
-
replace the
_count
attribute with a property that returns the length of_instances
; -
add a
self._instances.pop(self._instances.index(obj))
towrapper
.
class InstanceCounter:
def __init__(self, cls):
self._instances = []
self._count = 0
if hasattr(cls, "__del__"):
del_meth = cls.__del__
else:
del_meth = None
cls.__del__ = self._del_decorator(del_meth)
self._wrapped_cls = cls
@property
def instance_count(self):
return self._count
def __call__(self):
self._count += 1
obj = self._wrapped_cls()
# self._instances.append(obj) # When this line is uncommented...
return obj
def _del_decorator(self, del_method):
def wrapper(obj):
self._count -= 1
if del_method:
del_method(obj)
return wrapper
@InstanceCounter
class MyClass:
def __init__(self):
pass
def __del__(self): # ... this method will not be executed.
print(f"Deleting object of class {__class__}")
@InstanceCounter
class OtherClass:
def __init__(self):
pass
# Test cases with expected output.
my_a = MyClass()
print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
my_b = MyClass()
print(f"{MyClass.instance_count=}") # MyClass.instance_count=2
del my_a
print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
oth_c = OtherClass()
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
oth_d = OtherClass()
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=2
del oth_c
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
del oth_d
print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=0
Why is the __del__
method not called if an instance of the class is saved in the decorating class? How can I fix this?
答案1
得分: 2
在取消注释 self._instances.append(obj)
后,my_a = MyClass()
创建了两个对该对象的引用 - my_a
和 MyClass._instances[0]
。del my_a
仅删除了第一个引用,因此对象未被销毁,因此 __del__
方法不会被调用。
如果这不是您想要的效果,您可以存储弱引用的列表而不是普通引用。要做到这一点,只需将
self._instances.append(obj)
替换为
self._instances.append(weakref.ref(obj))
。
英文:
After uncommenting self._instances.append(obj)
, my_a = MyClass()
creates two references to the object - my_a
and MyClass._instances[0]
. del my_a
deletes only the first reference, so the object is not destroyed and, thus, __del__
method is not called.
If this is not what you want, you can store a list of weak references instead of normal references. To do this, just replace
self._instances.append(obj)
with
self._instances.append(weakref.ref(obj))
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论