如何让 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}
{}
我目前正在构建一个小库,我 运行 遇到了一个描述符问题:我创建了一个 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}
{}