`__del__`方法如果实例被存储,为什么不会被调用?

huangapple go评论115阅读模式
英文:

Why is `__del__` method not called if an instance is stored?

问题

为了加深我对装饰器的理解,我尝试提出了一个替代方案,用于 Tonie Victor 的博客 中的 count_instances 装饰器(在 类装饰器的常见用例 部分)。

与其使用函数,我使用一个类作为装饰器,并扩展了功能,以便在删除装饰类的实例时保持正确的计数。为此,我装饰了装饰类的 __del__ 方法。

实际上,在我的简单测试案例中,这确实按预期工作。然而,当我尝试将装饰器从一个实例 counter 更改为包含所有实例的实例 tracker 时,它突然不再按预期工作。

更具体地说,如果我将装饰类的实例添加到 InstanceCounter 类中的列表中,那么如果删除实例,(装饰的)__del__ 方法将不再被调用,计数也将不正确。

请注意,我尚未为 tracker 实现所有功能。我计划:

  1. 用一个返回 _instances 长度的属性来替换 _count 属性;

  2. wrapper 中添加 self._instances.pop(self._instances.index(obj))

  1. class InstanceCounter:
  2. def __init__(self, cls):
  3. self._instances = []
  4. self._count = 0
  5. if hasattr(cls, "__del__"):
  6. del_meth = cls.__del__
  7. else:
  8. del_meth = None
  9. cls.__del__ = self._del_decorator(del_meth)
  10. self._wrapped_cls = cls
  11. @property
  12. def instance_count(self):
  13. return self._count
  14. def __call__(self):
  15. self._count += 1
  16. obj = self._wrapped_cls()
  17. # self._instances.append(obj) # 当取消注释此行时...
  18. return obj
  19. def _del_decorator(self, del_method):
  20. def wrapper(obj):
  21. self._count -= 1
  22. if del_method:
  23. del_method(obj)
  24. return wrapper
  25. @InstanceCounter
  26. class MyClass:
  27. def __init__(self):
  28. pass
  29. def __del__(self): # ... 这个方法将不会被执行。
  30. print(f"Deleting object of class {__class__}")
  31. @InstanceCounter
  32. class OtherClass:
  33. def __init__(self):
  34. pass
  35. # 带有预期输出的测试案例。
  36. my_a = MyClass()
  37. print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
  38. my_b = MyClass()
  39. print(f"{MyClass.instance_count=}") # MyClass.instance_count=2
  40. del my_a
  41. print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
  42. oth_c = OtherClass()
  43. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
  44. oth_d = OtherClass()
  45. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=2
  46. del oth_c
  47. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
  48. del oth_d
  49. 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

  1. replace the _count attribute with a property that returns the length of _instances;

  2. add a self._instances.pop(self._instances.index(obj)) to wrapper.

  1. class InstanceCounter:
  2. def __init__(self, cls):
  3. self._instances = []
  4. self._count = 0
  5. if hasattr(cls, "__del__"):
  6. del_meth = cls.__del__
  7. else:
  8. del_meth = None
  9. cls.__del__ = self._del_decorator(del_meth)
  10. self._wrapped_cls = cls
  11. @property
  12. def instance_count(self):
  13. return self._count
  14. def __call__(self):
  15. self._count += 1
  16. obj = self._wrapped_cls()
  17. # self._instances.append(obj) # When this line is uncommented...
  18. return obj
  19. def _del_decorator(self, del_method):
  20. def wrapper(obj):
  21. self._count -= 1
  22. if del_method:
  23. del_method(obj)
  24. return wrapper
  25. @InstanceCounter
  26. class MyClass:
  27. def __init__(self):
  28. pass
  29. def __del__(self): # ... this method will not be executed.
  30. print(f"Deleting object of class {__class__}")
  31. @InstanceCounter
  32. class OtherClass:
  33. def __init__(self):
  34. pass
  35. # Test cases with expected output.
  36. my_a = MyClass()
  37. print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
  38. my_b = MyClass()
  39. print(f"{MyClass.instance_count=}") # MyClass.instance_count=2
  40. del my_a
  41. print(f"{MyClass.instance_count=}") # MyClass.instance_count=1
  42. oth_c = OtherClass()
  43. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
  44. oth_d = OtherClass()
  45. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=2
  46. del oth_c
  47. print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1
  48. del oth_d
  49. 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_aMyClass._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)).

huangapple
  • 本文由 发表于 2023年6月1日 03:27:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76376703.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定