为何将实例存入装饰器类时__del__方法未被调用?如何修复?
问题原因与解决方案
问题原因
当你将实例添加到装饰器的_instances列表时,列表持有对该实例的强引用。Python的垃圾回收机制仅在对象的引用计数降为0时,才会触发__del__方法并回收对象。由于装饰器的列表仍保留着实例的引用,即使你删除了my_a这类变量,对象的引用计数仍大于0,因此__del__不会被调用,计数也无法正确更新。
解决方案
使用Python标准库中的weakref.WeakSet存储实例。WeakSet会自动持有对象的弱引用,不会阻止对象被垃圾回收。当实例的最后一个强引用被删除时,垃圾回收器会回收该实例,自动将其从WeakSet中移除,同时触发实例的__del__方法。
修正后的代码
import weakref import gc # 用于强制触发垃圾回收,仅测试场景需要 class InstanceCounter: def __init__(self, cls): self._instances = weakref.WeakSet() self._wrapped_cls = cls @property def instance_count(self): return len(self._instances) def __call__(self): obj = self._wrapped_cls() self._instances.add(obj) return obj @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 gc.collect() # 强制触发垃圾回收,确保实例立即被回收 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 gc.collect() print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=1 del oth_d gc.collect() print(f"{OtherClass.instance_count=}") # OtherClass.instance_count=0
关键修改说明
- 替换为WeakSet:将
_instances从列表改为weakref.WeakSet,避免持有强引用导致实例无法被回收。 - 移除手动计数逻辑:不再需要
_count变量和__del__方法的装饰器,instance_count直接返回WeakSet的长度,自动反映存活实例数量。 - 强制垃圾回收(测试用):在删除实例后调用
gc.collect(),确保垃圾回收立即触发,测试时能看到计数实时更新。生产环境中通常无需手动调用,Python会自动管理垃圾回收。
内容的提问来源于stack exchange,提问作者Jan-Willem Lankhaar
相关产品推荐
相关产品推荐

