如果存储了实例,为什么不调用“__del__”方法?

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

提问人:Jan-Willem Lankhaar 提问时间:6/1/2023 最后编辑:wjandreaJan-Willem Lankhaar 更新时间:6/1/2023 访问量:82

问:

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

我没有使用函数,而是使用一个类作为装饰器,并且我扩展了功能以保持适当的计数,即使删除了修饰类的实例。为此,我装饰了装饰类的方法。__del__

事实上,这在我的简单测试用例中按预期工作。但是,当我尝试将装饰器从实例计数器更改为包含所有实例列表的实例跟踪器时,它突然不再按预期工作。

更具体地说,如果我将修饰类的实例添加到类中的列表中,则如果我删除实例并且计数错误,则不再调用(装饰)方法。InstanceCounter__del__

请注意,我尚未实现跟踪器的所有功能。我打算

  1. 将该特性替换为返回长度_count_instances;

  2. 将 a 添加到 .self._instances.pop(self._instances.index(obj))wrapper

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

如果类的实例保存在装饰类中,为什么不调用该方法?我该如何解决这个问题?__del__

python 覆盖 装饰器 del

评论

3赞 quamrana 6/1/2023
为什么你认为应该调用该方法?__del__()
4赞 slothrop 6/1/2023
“如果类的实例保存在装饰类中,为什么不调用 del 方法?”实例的引用计数(至少)为 1,因为在装饰对象的“self._instances”中引用了该实例。您可能想使用 weakrefs?docs.python.org/3/library/weakref.html
0赞 Jan-Willem Lankhaar 6/1/2023
@slothrop 如果我将行与(步骤 2)相加,引用计数不会减少 1 吗?我试过了,但没有用。pop
1赞 InSync 6/1/2023
您可能想通过 mCoding 观看此视频
1赞 slothrop 6/1/2023
我认为这行不通,因为存在一个先有鸡还是先有蛋的问题:如果没有那个“pop”,引用计数不会下降到 0,所以没有任何东西会触发对 del_decorator 包装器的调用,从而调用“pop”。

答:

2赞 Viktor Dremov 6/1/2023 #1

取消注释后,创建对对象的两个引用 - 和 。 仅删除第一个引用,因此不会销毁对象,因此不会调用方法。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))