Automatically delete an instance of a class when one of its attributes becomes dead
Customization
Let's say I have Snit
:
class Snit(): pass
And a Snot
that contains weak references up to, say, four Snit
s:
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)
I also have a Snot
factory:
def snot_factory(snits,w,x,y,z):
return Snot(snits[w],snits[x],snits[y],snits[z])
And list
from Snit
(a snit_list
kind of):
snit_list = []
for i in range(12):
snit_list.append(Snit())
Now I am creating a list Snot
using Snit
in mine snit_list
:
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]))
Problem
Oops! I don't need it anymore snit_list[3]
, so I'll go ahead and remove it:
snit_list.pop(3)
But now I have Snot
where it is with the dead Snit
:
snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>
It doesn't hold up! A Snot
with the dead Snit
is obviously complete nonsense.
So I would really like to see any references Snot
to at least return as None
after destroying one or more of it Snit
. But ideally it would be even better to be Snot
automatically removed from the list snot_list
( len(snot_list)
reduced by the number of Snot
s removed ).
What's a good way to do this?
Clarification:
A Snot
is an object that should only exist when a valid set exists Snit
("valid" means it has the same number of defined ones Snit
that have been initialized), with the following behavior:
- If any of the
Snit
inSnot
goes away (when strong links don't stay),Snot
should go away too (that's why I sets1
,s2
etc. to be weak links). Note that aSnot
could have been initialized with 4, 3, 2, or 1Snit
. The numberSnit
does not matter, which is important for deathSnit
. - If any
Snot
containing a link toSnit
go away remainsSnit
. - OPTIONAL: upon deletion,
Snot
the data structure containing the reference to the objectSnot
is also updated (Snot
getspop
ped) - OPTIONAL: When ALL of
Snots
this odd referenceSnit
disappears, the valueSnit
also disappears, and any data structures containingSnit
are updated as in # 3 (Snit
getspop
PED).
So, a perfect solution would allow me to set up things like this so that I can write code like this:
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
However, if this is too complicated, I would be fine:
assert snots[12] == None
assert snots[45] == None
I can change the situation a little. For example, if it simplifies the design, I think it would be a good idea to remove the weak references to Snit
s, or perhaps move them instead of a list Snit
instead of being members of the Snot
weak references (although I don't see how these changes would improve the situation).
I also looked into subclassing Snot
- ClearSnot
with 1 Snit
, YellowSnot
with 2 Snit
s, GreenSnot
with 3
Snit`, etc. I'm not sure if this will make things easier or more complex.
source to share
Nothing is truly automatic. You need to either execute a function that you manually run to check for dead Snit
s, or have a function that is a part Snot
that gets called whenever something interesting happens Snot
to check, and remove, dead Snit
s.
For example:
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
The interesting part is adding that the call _remove_dead_snits
for every interesting interaction with a Snot
- for example __getitem__
, __iter__
and whatever you can do with it.
Actually, after thinking about it a bit, if you only have four possible Snit
for each Snot
, you can use a descriptor SnitRef
- here's the code with some changes to your original:
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)
and at startup:
[Snit(0), Snit(1), Snit(2), Snit(3)]
Snit(2)
[Snit(0), Snit(1), Snit(3)]
None
source to share
Ok, so you have Snot
with a variable number of Snit
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
In fact, if an instance of a class disappears, it will do more work (like having dict
in the class to keep track of all of them, and then other data structures will only use weak references, but that can get ugly quickly), so the next best thing will equal it None
when any of her is Snit
gone.
I see that snits
and snots
are both list
- if the order of importance? If order is not important, you can use set
instead, and then it would be possible to have an execution solution in which the dead is Snot
actually removed from the data structure, but that would add complexity: everyone Snot
would have to keep track of which data line they were in. and each Snit
would have to keep the list Snot
they were on, and the magic would have to live in __del__
, which could lead to other problems ...
source to share