views:

490

answers:

3

I'm in need of a list of weak references that deletes items when they die. Currently the only way I have of doing this is to keep flushing the list (removing dead references manually).

I'm aware there's a WeakKeyDictionary and a WeakValueDictionary, but I'm really after a WeakList, is there a way of doing this?

Here's an example:

import weakref

class A(object):
    def __init__(self):
       pass

class B(object):
    def __init__(self):
        self._references = []

    def addReference(self, obj):
        self._references.append(weakref.ref(obj))

    def flush(self):
        toRemove = []

        for ref in self._references:
            if ref() is None:
                toRemove.append(ref)

        for item in toRemove:
            self._references.remove(item)

b = B()

a1 = A()
b.addReference(a1)
a2 = A()
b.addReference(a2)

del a1
b.flush()
del a2
b.flush()
A: 

Your question seems a bit odd... the whole idea about weak references is that the reference can survive even after the object that is referred to is dead.

To quote from the documentation

Normally, application code that needs to use a reference object should follow this pattern:

# r is a weak reference object
o = r()
if o is None:
    # referent has been garbage collected
    print "Object has been deallocated; can't frobnicate."
else:
    print "Object is still live!"
    o.do_something_useful()

So, if for example you'd like to iterate over all non-dead entries in a list of weakrefs, you could use something like this:

for obj in [o() for o in wlist if o() is not None]:
    foo(obj)

(Other than the example from the documentation, this is not threadsafe, though!)

I guess you could write a wrapper class around a list that you can use to iterate only over non-dead references, but somehow i doubt that this is worth the effort....

hop
In fact each object in the list is referenced by multiple objects (only being deallocated when all references are removed). I can leave the dead objects in the list and then only iterate over the alive ones, but this seems a little wasteful...
Dan
you could iterate over all items, use the live ones and delete the dead ones... do you want to do anything with the list besides iterating over it? holding an index into the WeakList doesn't seem to make much sense to me.
hop
Java has something called a ReferenceQueue where weak references get added to the queue when they're dead, for notification of your application... http://www.ibm.com/developerworks/java/library/j-refs/
Jason S
+1  A: 

You could implement it yourself, similarly to how you have done, but with a list subclass that calls flush() before attempting to access an item.

Obviously you don't want to do this on every access, but you can optimize this by setting a callback on the weak reference to mark the list dirty when something dies. Then you only need to flush the list when something has died since the last access.

Here's a list class implemented using this method. (Note that it's not tested much, and some methods aren't implemented very efficiently (eg. those which just convert to a real list and call the method on that), but it should be a reasonable starting point:

import weakref

class WeakList(list):
    def __init__(self, seq=()):
        list.__init__(self)
        self._refs = []
        self._dirty=False
        for x in seq: self.append(x)

    def _mark_dirty(self, wref):
        self._dirty = True

    def flush(self):
        self._refs = [x for x in self._refs if x() is not None]
        self._dirty=False

    def __getitem__(self, idx):
        if self._dirty: self.flush()
        return self._refs[idx]()

    def __iter__(self):
        for ref in self._refs:
            obj = ref()
            if obj is not None: yield obj

    def __repr__(self):
        return "WeakList(%r)" % list(self)

    def __len__(self):
        if self._dirty: self.flush()
        return len(self._refs)

    def __setitem__(self, idx, obj):
        if isinstance(idx, slice):
            self._refs[idx] = [weakref.ref(obj, self._mark_dirty) for x in obj]
        else:
            self._refs[idx] = weakref.ref(obj, self._mark_dirty)

    def __delitem__(self, idx):
        del self._refs[idx]

    def append(self, obj):
        self._refs.append(weakref.ref(obj, self._mark_dirty))

    def count(self, obj):
        return list(self).count(obj)

    def extend(self, items):
        for x in items: self.append(x)

    def index(self, obj):
        return list(self).index(obj)

    def insert(self, idx, obj):
        self._refs.insert(idx, weakref.ref(obj, self._mark_dirty))

    def pop(self, idx):
        if self._dirty: self.flush()
        obj=self._refs[idx]()
        del self._refs[idx]
        return obj

    def remove(self, obj):
        if self._dirty: self.flush() # Ensure all valid.
        for i, x in enumerate(self):
            if x == obj:
                del self[i]

    def reverse(self):
        self._refs.reverse()

    def sort(self, cmp=None, key=None, reverse=False):
        if self._dirty: self.flush()
        if key is not None:
            key = lambda x,key=key: key(x())
        else:
            key = apply
        self._refs.sort(cmp=cmp, key=key, reverse=reverse)

    def __add__(self, other):
        l = WeakList(self)
        l.extend(other)
        return l

    def __iadd__(self, other):
        self.extend(other)
        return self

    def __contains__(self, obj):
        return obj in list(self)

    def __mul__(self, n):
        return WeakList(list(self)*n)

    def __imul__(self, n):
        self._refs *= n
        return self

[Edit] Add a more complete list implementation.

Brian
A: 

Why can't you just do it like this:

import weakref

class WeakList(list):
    def append(self, item):
        list.append(self, weakref.ref(item), self.remove)

And then do similar for __iadd__, extend etc.

Longpoke