如何让 python 描述符知道拥有 class 的实例已被删除

How to let the python descriptor know that an instance of the owning class was deleted

我目前正在构建一个小库,我 运行 遇到了一个描述符问题:我创建了一个 python 描述符,它必须为单独的 class 存储值,但我不想使用 class 作为存储。而且我不希望用户必须继承任何这些描述符才能工作。

但是当对象的实例被删除时,我想删除它在该实例的描述符中的数据。 (该实例可以删除,因为描述符不包含对它的引用,我在字典中用它们的 id 索引了那些)

而且必须这样做,因为可以创建另一个实例,具有相同的 id,导致 'data transfer' 从旧对象到新对象,这对 任何方式。

有没有办法让描述符知道 class 描述符所属的实例已被删除?

__delete__ 仅在删除属性时触发,而不是在删除实例时触发)

这里有一些代码可以向您展示这是怎么回事:

class CounterDescriptor(object):  # I need the answer for something different, but the problem is the same. My code is just much larger and a whole lot more complicated.
    def __init__(self) -> None:
        self.counts = {}

    def __get__(self, instance: object, owner: type) -> object:
        instance_id = id(instance)
        if instance_id not in self.counts:
            self.counts[instance_id] = 0
        self.counts[instance_id] += 1
        return self.counts[instance_id]

    def __set__(self, instance: object, value: int) -> None:
        self.counts[id(instance)] = int(value)


class SomethingUsingTheCounterDescriptor(object):
    x = CounterDescriptor()

x = SomethingUsingTheCounterDescriptor()
x.x  # \-> count goes one higher (-> now it is 1)
del x  # Now I want to get rid of the data stored for x in the CounterDescriptor.

先谢谢你了,

代号Lambda

如果您的实例可用,您可以改用 WeakKeyDictionary 标准词典:

from weakref import WeakKeyDictionary

class CounterDescriptor(object): 

    def __init__(self) -> None:
        self.counts = WeakKeyDictionary()

    def __get__(self, instance: object, owner: type) -> object:
        if instance is None:
            return self.counts
        if instance not in self.counts:
            self.counts[instance] = 0
        self.counts[instance] += 1
        return self.counts[instance]

    def __set__(self, instance: object, value: int) -> None:
        self.counts[instance] = int(value)


class SomethingUsingTheCounterDescriptor(object):
    x = CounterDescriptor()

s = SomethingUsingTheCounterDescriptor()
s.x  # \-> count goes one higher (-> now it is 1)
s.x
print(dict(SomethingUsingTheCounterDescriptor.x))
del s  # Now I want to get rid of the data stored for x in the CounterDescriptor.
print(dict(SomethingUsingTheCounterDescriptor.x))

输出:

{<__main__.SomethingUsingTheCounterDescriptor object at 0x1032a72b0>: 2}
{}

我将使用以下方法:

import sys
from typing import Dict, List


class CounterDescriptor(object): 

    def __init__(self) -> None:
        self.counts = {}  # type: Dict[int, int]
        self.references = []  # type: List[object]

    def _do_cleanup(self) -> None:
        deleted = 0
        for i, v in enumerate(self.references[:]):
            if sys.getrefcount(v) == 7:
            # found by trying. I know of 4 references: self.references, the copy, v and the argument submitted to getrefcount
                del self.references[i - deleted]
                deleted += 1

    def __get__(self, instance: object, owner: type) -> object:
        self._do_cleanup()

        instance_id = id(instance)
        if instance not in self.counts:
            self.counts[instance_id] = 0
            self.references.append(instance)
        self.counts[instance_id] += 1
        return self.counts[instance_id]

    def __set__(self, instance: object, value: int) -> None:
        self._do_cleanup()

        instance_id = id(instance)
        if instance_id not in self.counts:
            self.references.append(instance)
        self.counts[instance_id] = int(value)


class SomethingUsingTheCounterDescriptor(object):
    x = CounterDescriptor()

s = SomethingUsingTheCounterDescriptor()
s.x  # \-> count goes one higher (-> now it is 1)
s.x
print(SomethingUsingTheCounterDescriptor.x.counts)
del s
print(SomethingUsingTheCounterDescriptor.x.counts)

它产生了预期的结果。 在我的 运行:

{4323824640: 1}
{}

虽然我在尝试解决这个问题时很早就有了这个想法,但我放弃了它,因为它不是很 pythonic。 但我要感谢@Mike Müller,因为他让我重新考虑引用计数,这让我重新开始学习它。

我会在我必须等待两天后将此问题标记为已解决,尽管它确实不是,因为这个解决方案一点也不漂亮。

另一个不需要可哈希实例的弱引用版本:

from weakref import ref


class CounterDescriptor(object): 

    def __init__(self) -> None:
        self.counts = {}
        self.refs = {}

    def _clean_up(self):
        for id_, ref in self.refs.items():
            if ref() is None:
                del self.counts[id_]

    def __get__(self, instance: object, owner: type) -> object:
        self._clean_up()
        inst_id = id(instance)
        if instance is None:
            return self.counts
        if inst_id not in self.counts:
            self.counts[inst_id] = 0
            self.refs[inst_id] = ref(instance)
        self.counts[inst_id] += 1
        return self.counts[inst_id]

    def __set__(self, instance: object, value: int) -> None:
        self._clean_up()
        inst_id = id(instance)
        self.counts[inst_id] = int(value)
        self.refs[inst_id] = ref(instance)

class SomethingUsingTheCounterDescriptor(object):
    x = CounterDescriptor()
s = SomethingUsingTheCounterDescriptor()
s.x
s.x
print(SomethingUsingTheCounterDescriptor.x)
del s
print(SomethingUsingTheCounterDescriptor.x)

输出:

{4460071120: 2}
{}