20

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()
Dan
  • 33,953
  • 24
  • 61
  • 87
  • die = weak reference becomes invalid ("dead"). reference = a weak reference (http://docs.python.org/library/weakref.html). flush = removing manually = iterate over all references in list removing those that are invalid. – Dan Mar 24 '09 at 16:05
  • I wrote your WeakList. See here http://stackoverflow.com/questions/7828444/indexable-weak-ordered-set-in-python – Neil G Oct 19 '11 at 21:50

6 Answers6

6

You can use WeakSet from the very same weakref module (it's actually defined elsewhere by the way, but it's imported there).

>>> from weakref import WeakSet
>>> s = WeakSet()
>>> class Obj(object): pass # can't weakref simple objects
>>> a = Obj()
>>> s.add(a)
>>> print len(s)
1
>>> del a
>>> print len(s)
0
Neuron
  • 5,141
  • 5
  • 38
  • 59
rewritten
  • 16,280
  • 2
  • 47
  • 50
  • 5
    A WeakSet can only store hashable objects. – Peter Graham Jun 29 '11 at 02:13
  • 6
    it also has the shortcoming of not preserving the order of it's entries. – SingleNegationElimination Jul 08 '11 at 18:15
  • 3
    @PeterGraham and TokenMacGuy: the OP didn't state his requirements as for ordering. So if he was just wanted an [unordered] bag of weakly-reffed things, and if those things are hashable, the WeakSet idea looks workable. If the things are objects of his own design, perhaps he can meet the hashable requirement in his own object. – Dan H Jul 18 '12 at 16:36
6

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
Neuron
  • 5,141
  • 5
  • 38
  • 59
Brian
  • 116,865
  • 28
  • 107
  • 112
  • 1
    How does that behave when the list size changes during an iteration? I'm talking about the loop in `__iter__` and that in `remove`. – Niriel May 30 '12 at 10:14
5

As I needed a weakref list like you, I've made one and publish it on pypi.

now you can do:

pip install weakreflist

then:

from weakreflist import WeakList
gregorySalvan
  • 371
  • 2
  • 9
3

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. Works for me.

El Ninja Trepador
  • 1,013
  • 1
  • 10
  • 14
0

Use a callback function passed to second argument of a weakref.

This code should function:

import weakref

class WeakRefList(list):

    def add_reference(self, obj):
        self._references.append(weakref.proxy(obj, self.remove))
Neuron
  • 5,141
  • 5
  • 38
  • 59
gregorySalvan
  • 371
  • 2
  • 9
0

How do you plan on using B? The only thing I ever do with the weakref list I built is iterate over it, so its implementation is simple:

import weakref

class WeakRefList(object):
    "weakref psuedo list"
    def __init__(yo):
        yo._items = list()
    def __iter__(yo):
        yo._items = [s for s in yo._items if s() is not None]
        return (s() for s in yo._items if s() is not None)
    def __len__(yo):
        yo._items = [s for s in yo._items if s() is not None]
        return len(yo._items)
    def append(yo, new_item):
        yo._items.append(weakref.ref(new_item))
        yo._items = [s for s in yo._items if s() is not None]
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237