我目前正在构建一个小型库,我遇到了描述符的问题:我创建了一个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.
我只是提前感谢你,
CodenameLambda
答案 0 :(得分:2)
如果您的实例可以使用,则可以使用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}
{}
答案 1 :(得分:1)
另一个弱引用的版本,不需要hashable实例:
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}
{}
答案 2 :(得分:0)
我将使用以下方法:
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。 但是我要感谢@MikeMüller,因为他让我再次考虑引用计数,这让我再次接受它。
在我必须等待的两天之后,我会将这个问题标记为已解决,尽管事实并非如此,因为这个解决方案不是很漂亮。