Reputation: 45261
Say I have a Snit
:
class Snit(): pass
And a Snot
, which contains weak references to 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 a list
of Snit
s (a snit_list
as it were):
snit_list = []
for i in range(12):
snit_list.append(Snit())
Now I make a list of Snot
s using the Snit
s in my 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]))
Whoops! I don't need snit_list[3]
anymore, so I'll go ahead and remove it:
snit_list.pop(3)
But now I have a Snot
hanging out there with a dead Snit
:
snot_list[0].s4 # <weakref at 0x00BlahBlah; dead>
This cannot stand! A Snot
with a dead Snit
is - obviously - total nonsense.
So I would really like for any references to the Snot
to at least return as None
after one or more of its Snit
s has been destroyed. But ideally, it would be even better for the Snot
to be automatically removed from the snot_list
list as well (len(snot_list)
shrinks by the number of removed Snot
s).
What's a good way of going about this?
A Snot
is an object that should only exist when there is a valid set of Snit
s ("valid" means it has the same number of defined Snit
s it was initialized with), with the following behavior:
Snit
in a Snot
goes away (when no strong references remain), the Snot
should also go away (this is why I have set the s1
, s2
, etc to be weak references). Note that a Snot
could have been initialized with 4, 3, 2, or 1 Snit
. The number of Snit
s doesn't matter, the death of the Snit
is what matters. Snot
that contains a reference to a Snit
goes away, the Snit
remains. Snot
is deleted, the data structure containing the reference to the Snot
object is updated as well (the Snot
gets pop
ped)Snots
that reference a certain Snit
are gone, the Snit
goes away too, and any data structures containing the Snit
are updated as in #3 (the Snit
gets pop
ped).So the ideal solution will allow me to set things up such 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 hard, I'd be fine with:
assert snots[12] == None
assert snots[45] == None
I am open to changing things around somewhat. For example, if it makes the design easier, I think it would be fine to remove the weak references to the Snit
s, or to maybe move them instead to the list of Snit
s instead of having the Snot
members be weak refs (though I don't see how either of these changes would improve things).
I have also considered creating Snot
subclasses - ClearSnot
with 1 Snit
, YellowSnot
with 2 Snit
s, GreenSnot
with 3 Snit
s, etc. I'm uncertain if this would make things easier to maintain, or more difficult.
Upvotes: 3
Views: 225
Reputation: 69051
Okay, so you have a Snot
with a variable amount 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
Actually having the class instance disappear is going to take more work (such as having dict
on the class to track them all, and then other data structures would just use weak-references -- but that could get ugly quick), so the next best thing will be having it become equal to None
when any of its Snit
s goes away.
I see that snits
and snots
are both list
s -- is order important? If order is not important you could use set
s instead, and then it would be possible to have a performant solution where the the dead snot
is actually removed from the data structure -- but it would add complexity: each Snot
would have to keep track of which data struture it was in, and each Snit
would have to keep a list of which Snot
s it was in, and the magic would have to live in __del__
which can lead to other problems...
Upvotes: 1
Reputation: 69051
Nothing is truly automatic. You'll need to either have a function that you run manually to check for dead Snit
s, or have a function that is part of Snot
that is called whenever anything interesting happens to a Snot
to check for, 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 fun part is adding that call to _remove_dead_snits
for every interesting interaction with a Snot
-- such as __getitem__
, __iter__
, and whatever else you may do with it.
Actually, thinking a bit more about this, if you only have the four possible Snit
s per each Snot
you could use a SnitRef
descriptor -- 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 when run:
[Snit(0), Snit(1), Snit(2), Snit(3)]
Snit(2)
[Snit(0), Snit(1), Snit(3)]
None
Upvotes: 2