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



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

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

必须这样做,因为可以创建另一个具有相同 id 的实例,从而导致从旧对象到新对象的"数据传输",这没有任何帮助。

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

__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.

我只想提前谢谢你,

代号拉姆达

如果您的实例是可抢购的,则可以改用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}
{}

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

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}
{}

我将使用以下方法:

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,因为他让我再次考虑参考计数,并引导我再次拿起它。

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

最新更新