2016-04-23 42 views
2

我目前正在构建一个小型库,并且遇到了描述符问题:我创建了一个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. 

我会只是预先感谢您,

CodenameLambda

+0

请提供一些示例代码。我不明白::我创建了一个python描述符,它必须存储单独类的值,但我不想将该类用作存储。 PS:https://docs.python.org/3/reference/datamodel。html#实现描述符以下方法仅适用于包含方法的类的实例(所谓的描述符类)出现在所有者类中时(描述符必须位于所有者的类字典或类字典中,的父母)。 –

回答

1

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

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

如果对象不存在,弱引用是否保证产生无?或者,如果某人非常非常不幸,它会产生另一个对象吗? – CodenameLambda

+1

文档:*返回对象的弱引用。如果指示对象仍然存在,则可以通过调用参考对象来检索原始对象;如果指示对象不再有效,则调用引用对象将导致返回“无”。* –

+0

谢谢。 (*原始对象*意味着它将是安全的,我猜测) – CodenameLambda

2

如果你的情况是hasbable,你可以使用一个WeakKeyDictionary instea标准字典的d :

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

'如果一个对象具有在其生命周期中从不改变的散列值(它需要__hash __()方法)并且可以与其他对象进行比较(它需要__eq __()方法),则该对象是可散列的。比较相等的哈希对象必须具有相同的哈希值。[见这里](https://docs.python.org/3/glossary.html)。这并没有帮助,考虑到可平等的可散列对象必须具有相同的散列,因此如果不打破它们,我就无法处理它们。 (因为我不想在结果对象中改变任何东西,我的描述符应该可以代替现有的属性。) – CodenameLambda

+0

但是这可以在其他类似的情况下使用。我现在要用我现在使用的解决方案写一个答案,虽然它不是很漂亮。随意改进它。 – CodenameLambda

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

虽然我有这个想法,试图解决这个问题相当早,我放弃它,因为它是不是很 Python的。 但是我必须感谢@MikeMüller,因为他让我再次考虑参考计数,这让我再次捡起它。

我将这个问题标记为在我需要等待两天后解决,但实际上并不是这样,因为这个解决方案不是什么美丽的东西。

相关问题