ActiveState Code

Recipe 528879: Weak Key and Value Dictionary


A dict in which items get deleted if either the key or the value of the item is garbage collected.

Python
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# By Eyal Lotem and Yair Chuchem, 2007

import weakref

class WeakKeyValueDict(object):
    """
    A dict in which items are removed whenever either key or value are
    garbage-collected.
    """
    def __init__(self, *args, **kw):
        init_dict = dict(*args, **kw)
        
        self._d = weakref.WeakKeyDictionary(
            (key, self._create_value(key, value))
            for key, value in init_dict.iteritems())

    def _create_value(self, key, value):
        key_weakref = weakref.ref(key)
        def value_collected(wr):
            del self[key_weakref()]
        return weakref.ref(value, value_collected)

    def __getitem__(self, key):
        return self._d[key]()
    
    def __setitem__(self, key, value):
        self._d[key] = self._create_value(key, value)

    def __delitem__(self, key):
        del self._d[key]

    def __len__(self):
        return len(self._d)

    def __cmp__(self, other):
        try:
            other_iteritems = other.iteritems
        except AttributeError:
            return NotImplemented
        return cmp(sorted(self.iteritems()),
                   sorted(other_iteritems()))

    def __hash__(self):
        raise TypeError("%s objects not hashable" % (self.__class__.__name__,))

    def __contains__(self, key):
        return key in self._d

    def __iter__(self):
        return self.iterkeys()

    def iterkeys(self):
        return self._d.iterkeys()

    def keys(self):
        return list(self.iterkeys())

    def itervalues(self):
        for value in self._d.itervalues():
            yield value()

    def values(self):
        return list(self.itervalues())
    
    def iteritems(self):
        for key in self._d:
            yield self._d[key]()

    def items(self):
        return list(self.iteritems())

    def update(self, other):
        for key, value in other.iteritems():
            self[key] = value

    def __repr__(self):
        return repr(self._d)

    def clear(self):
        self._d.clear()

    def copy(self):
        return WeakKeyValueDict(self)

    def get(self, key, default=None):
        if key in self:
            return self[key]
        return default

    def has_key(self, key):
        return key in self

    def pop(self, key, *args):
        if args:
            return self._pop_with_default(key, *args)
        return self._pop(key)

    def _pop(self, key):
        return self._d.pop(key)()
    
    def _pop_with_default(self, key, default):
        if key in self:
            return self._d.pop(key)
        return default

    def popitem(self):
        key, value = self._d.popitem()
        return key, value()

    def setdefault(self, key, default):
        if key in self:
            return self[key]
        self[key] = default
        return default

Discussion

The weakref module implements WeakKeyDictionary and WeakValueDictionary. Along with the builtin dict those are three out of four* combinations of dict weakness, so here is the missing combination. In several occasions we wanted it. At every time, instead of implementing it we solved the problem in a different way or figured out that it doesn't really helps us. Maybe it will help you.

Sign in to comment