当类实例的一个属性失效时,自动删除该类实例



设置

假设我有Snit

class Snit(): pass

还有一个Snot,其中包含对最多四个Snit的弱引用:

import weakref
class Snot():
    def __init__(self,s1=None,s2=None,s3=None,s4=None):
        self.s1 = weakref.ref(s1)
        self.s2 = weakref.ref(s2)
        self.s3 = weakref.ref(s3)
        self.s4 = weakref.ref(s4)

我还有一个Snot工厂:

def snot_factory(snits,w,x,y,z):
    return Snot(snits[w],snits[x],snits[y],snits[z])

还有 Snit s 的list(可以说是snit_list):

snit_list = []
for i in range(12):
    snit_list.append(Snit())

现在,我在snit_list中使用Snit s列出了Snot

snot_list = []
for i in range(3):
    snot_list.append(snot_factory(snit_list[4*i],snit_list[4*i+1],snit_list[4*i+2],snit_list[4*i+3]))

问题所在

哎 呦!我不再需要snit_list[3],所以我会继续删除它:

snit_list.pop(3)

但是现在我有一个Snot和一个死Snit在那里闲逛:

snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>

这不能忍受!一个死SnitSnot显然是完全胡说八道。

因此,我真的很想在Snot的一个或多个Snit被摧毁后至少以None的身份返回。但理想情况下,最好也自动从snot_list列表中删除Snotlen(snot_list)会缩小删除Snot的数量)。

解决这个问题

的好方法是什么?

澄清:

Snot是一个对象,仅当存在一组有效的 Snit s 时才存在("有效"表示它具有与初始化时相同数量的已定义Snit),具有以下行为:

  1. 如果Snot中的任何一个Snit消失(当没有强引用时),Snot也应该消失(这就是为什么我将s1s2等设置为弱引用)。请注意,Snot可能已初始化为 4、3、2 或 1 SnitSnit的数量并不重要,Snit的死亡才是最重要的。
  2. 如果包含对Snit引用的任何Snot消失,则Snit仍然存在。
  3. 可选:删除Snot时,包含对Snot对象的引用的数据结构也会更新(Snot pop
  4. 可选:当引用某个Snit的所有Snots都消失时,Snit也会消失,并且包含Snit的任何数据结构都会更新为 #3(Snit pop ped)。

因此,理想的解决方案将允许我进行设置,以便我可以编写如下代码:

snits = get_snits_list(some_input_with_10000_snits)
snots = get_snots_list(some_cross_referenced_input_with_8000_snots)
#e.g.: the input file will say:
#snot number 12 is made of snits 1, 4, 7
#snot number 45 is made of snits 8, 7, 0, 14
do_stuff_with_snits()
snits.pop(7) #snit 7 is common to snot 12 and 45
assert len(snots) == 7998 #snots 12 and 45 have been removed

但是,如果这太难了,我会很好:

assert snots[12] == None
assert snots[45] == None

我愿意改变一些事情。例如,如果它使设计更容易,我认为可以删除对 Snit s 的弱引用,或者将它们移动到 Snit s 列表中,而不是让Snot成员成为弱引用(尽管我看不出这些更改中的任何一个会如何改善事情)。

我还考虑过创建Snot子类 - ClearSnot 具有 1 个SnitYellowSnot具有 2 个Snit 秒,GreenSnot具有 3 个Snit秒,等等。我不确定这是否会使事情更容易维护,或者更困难。

没有什么是真正自动的。 您需要有一个手动运行的函数来检查死Snit,或者有一个函数,该函数是Snot的一部分,每当Snot发生任何有趣的事情来检查和删除死Snit时都会调用该函数。

例如:

class Snot:
    ...
    def __repr__(self):
        # check for and remove any dead Snits
        self._remove_dead_snits()
        return ...
    def _remove_dead_snits(self):
        if self.s1() is None:
             self.s1 = None
        ... # and so on and so forth

有趣的部分是添加对_remove_dead_snits的调用,以便与Snot进行每次有趣的交互 - 例如__getitem____iter__,以及你可以用它做的任何其他事情。


实际上,再

考虑一下,如果你每个Snot只有四个可能的Snit,你可以使用SnitRef描述符 - 这是代码,对原始代码进行了一些更改:

import weakref
class Snit(object):
    def __init__(self, value):
        self.value = value  # just for testing
    def __repr__(self):
        return 'Snit(%r)' % self.value
class SnitRef(object):   # 'object' not needed in Python 3
    def __get__(self, inst, cls=None):
        if inst is None:
            return self
        return self.ref()  # either None or the obj
    def __set__(self, inst, obj):
        self.ref = weakref.ref(obj)

class Snot(object):
    s0 = SnitRef()
    s1 = SnitRef()
    s2 = SnitRef()
    s3 = SnitRef()
    def __init__(self,s0=None,s1=None,s2=None,s3=None):
        self.s0 = s0
        self.s1 = s1
        self.s2 = s2
        self.s3 = s3
snits = [Snit(0), Snit(1), Snit(2), Snit(3)]
print snits
snot = Snot(*snits)
print(snot.s2)
snits.pop(2)
print snits
print(snot.s2)

运行时:

[Snit(0), Snit(1), Snit(2), Snit(3)]
Snit(2)
[Snit(0), Snit(1), Snit(3)]
None

好的,所以你有一个SnotSnit s的数量是可变的。

class Snot(object):
    def __init__(self, *snits):
        self.snits = [weakref.ref(s) for s in snits]
    def __eq__(self, other):
        if not isinstance(other, self.__class__) and other is not None:
            return NotImplemented
        # are all my snits still valid
        valid = all(s() for s in self.snits)
        if other is None:
            return not valid  # if valid is True, we are not equal to None
        else:
            # whatever it takes to see if this snot is the same as the other snot

实际上,让类实例消失需要更多的工作(例如在类上dict来跟踪它们,然后其他数据结构只会使用弱引用 - 但这可能会很快变得丑陋),所以下一个最好的事情是让它变得等于None当它的任何Snit消失时。


我看到snitssnots都是list - 顺序重要吗? 如果顺序不重要,你可以改用set s,然后有可能有一个高性能的解决方案,其中死snot实际上从数据结构中删除 - 但它会增加复杂性:每个Snot都必须跟踪它所在的数据结构, 每个Snit都必须保留一份清单,列出它在哪个Snot,而魔法必须存在于__del__,这可能会导致其他问题......

最新更新