• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Weak reference support for Python.
2
3This module is an implementation of PEP 205:
4
5http://www.python.org/dev/peps/pep-0205/
6"""
7
8# Naming convention: Variables named "wr" are weak reference objects;
9# they are called this instead of "ref" to avoid name collisions with
10# the module-global ref() function imported from _weakref.
11
12import UserDict
13
14from _weakref import (
15     getweakrefcount,
16     getweakrefs,
17     ref,
18     proxy,
19     CallableProxyType,
20     ProxyType,
21     ReferenceType)
22
23from _weakrefset import WeakSet, _IterationGuard
24
25from exceptions import ReferenceError
26
27
28ProxyTypes = (ProxyType, CallableProxyType)
29
30__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
31           "WeakKeyDictionary", "ReferenceError", "ReferenceType", "ProxyType",
32           "CallableProxyType", "ProxyTypes", "WeakValueDictionary", 'WeakSet']
33
34
35class WeakValueDictionary(UserDict.UserDict):
36    """Mapping class that references values weakly.
37
38    Entries in the dictionary will be discarded when no strong
39    reference to the value exists anymore
40    """
41    # We inherit the constructor without worrying about the input
42    # dictionary; since it uses our .update() method, we get the right
43    # checks (if the other dictionary is a WeakValueDictionary,
44    # objects are unwrapped on the way out, and we always wrap on the
45    # way in).
46
47    def __init__(self, *args, **kw):
48        def remove(wr, selfref=ref(self)):
49            self = selfref()
50            if self is not None:
51                if self._iterating:
52                    self._pending_removals.append(wr.key)
53                else:
54                    del self.data[wr.key]
55        self._remove = remove
56        # A list of keys to be removed
57        self._pending_removals = []
58        self._iterating = set()
59        UserDict.UserDict.__init__(self, *args, **kw)
60
61    def _commit_removals(self):
62        l = self._pending_removals
63        d = self.data
64        # We shouldn't encounter any KeyError, because this method should
65        # always be called *before* mutating the dict.
66        while l:
67            del d[l.pop()]
68
69    def __getitem__(self, key):
70        o = self.data[key]()
71        if o is None:
72            raise KeyError, key
73        else:
74            return o
75
76    def __delitem__(self, key):
77        if self._pending_removals:
78            self._commit_removals()
79        del self.data[key]
80
81    def __contains__(self, key):
82        try:
83            o = self.data[key]()
84        except KeyError:
85            return False
86        return o is not None
87
88    def has_key(self, key):
89        try:
90            o = self.data[key]()
91        except KeyError:
92            return False
93        return o is not None
94
95    def __repr__(self):
96        return "<WeakValueDictionary at %s>" % id(self)
97
98    def __setitem__(self, key, value):
99        if self._pending_removals:
100            self._commit_removals()
101        self.data[key] = KeyedRef(value, self._remove, key)
102
103    def clear(self):
104        if self._pending_removals:
105            self._commit_removals()
106        self.data.clear()
107
108    def copy(self):
109        new = WeakValueDictionary()
110        for key, wr in self.data.items():
111            o = wr()
112            if o is not None:
113                new[key] = o
114        return new
115
116    __copy__ = copy
117
118    def __deepcopy__(self, memo):
119        from copy import deepcopy
120        new = self.__class__()
121        for key, wr in self.data.items():
122            o = wr()
123            if o is not None:
124                new[deepcopy(key, memo)] = o
125        return new
126
127    def get(self, key, default=None):
128        try:
129            wr = self.data[key]
130        except KeyError:
131            return default
132        else:
133            o = wr()
134            if o is None:
135                # This should only happen
136                return default
137            else:
138                return o
139
140    def items(self):
141        L = []
142        for key, wr in self.data.items():
143            o = wr()
144            if o is not None:
145                L.append((key, o))
146        return L
147
148    def iteritems(self):
149        with _IterationGuard(self):
150            for wr in self.data.itervalues():
151                value = wr()
152                if value is not None:
153                    yield wr.key, value
154
155    def iterkeys(self):
156        with _IterationGuard(self):
157            for k in self.data.iterkeys():
158                yield k
159
160    __iter__ = iterkeys
161
162    def itervaluerefs(self):
163        """Return an iterator that yields the weak references to the values.
164
165        The references are not guaranteed to be 'live' at the time
166        they are used, so the result of calling the references needs
167        to be checked before being used.  This can be used to avoid
168        creating references that will cause the garbage collector to
169        keep the values around longer than needed.
170
171        """
172        with _IterationGuard(self):
173            for wr in self.data.itervalues():
174                yield wr
175
176    def itervalues(self):
177        with _IterationGuard(self):
178            for wr in self.data.itervalues():
179                obj = wr()
180                if obj is not None:
181                    yield obj
182
183    def popitem(self):
184        if self._pending_removals:
185            self._commit_removals()
186        while 1:
187            key, wr = self.data.popitem()
188            o = wr()
189            if o is not None:
190                return key, o
191
192    def pop(self, key, *args):
193        if self._pending_removals:
194            self._commit_removals()
195        try:
196            o = self.data.pop(key)()
197        except KeyError:
198            if args:
199                return args[0]
200            raise
201        if o is None:
202            raise KeyError, key
203        else:
204            return o
205
206    def setdefault(self, key, default=None):
207        try:
208            wr = self.data[key]
209        except KeyError:
210            if self._pending_removals:
211                self._commit_removals()
212            self.data[key] = KeyedRef(default, self._remove, key)
213            return default
214        else:
215            return wr()
216
217    def update(self, dict=None, **kwargs):
218        if self._pending_removals:
219            self._commit_removals()
220        d = self.data
221        if dict is not None:
222            if not hasattr(dict, "items"):
223                dict = type({})(dict)
224            for key, o in dict.items():
225                d[key] = KeyedRef(o, self._remove, key)
226        if len(kwargs):
227            self.update(kwargs)
228
229    def valuerefs(self):
230        """Return a list of weak references to the values.
231
232        The references are not guaranteed to be 'live' at the time
233        they are used, so the result of calling the references needs
234        to be checked before being used.  This can be used to avoid
235        creating references that will cause the garbage collector to
236        keep the values around longer than needed.
237
238        """
239        return self.data.values()
240
241    def values(self):
242        L = []
243        for wr in self.data.values():
244            o = wr()
245            if o is not None:
246                L.append(o)
247        return L
248
249
250class KeyedRef(ref):
251    """Specialized reference that includes a key corresponding to the value.
252
253    This is used in the WeakValueDictionary to avoid having to create
254    a function object for each key stored in the mapping.  A shared
255    callback object can use the 'key' attribute of a KeyedRef instead
256    of getting a reference to the key from an enclosing scope.
257
258    """
259
260    __slots__ = "key",
261
262    def __new__(type, ob, callback, key):
263        self = ref.__new__(type, ob, callback)
264        self.key = key
265        return self
266
267    def __init__(self, ob, callback, key):
268        super(KeyedRef,  self).__init__(ob, callback)
269
270
271class WeakKeyDictionary(UserDict.UserDict):
272    """ Mapping class that references keys weakly.
273
274    Entries in the dictionary will be discarded when there is no
275    longer a strong reference to the key. This can be used to
276    associate additional data with an object owned by other parts of
277    an application without adding attributes to those objects. This
278    can be especially useful with objects that override attribute
279    accesses.
280    """
281
282    def __init__(self, dict=None):
283        self.data = {}
284        def remove(k, selfref=ref(self)):
285            self = selfref()
286            if self is not None:
287                if self._iterating:
288                    self._pending_removals.append(k)
289                else:
290                    del self.data[k]
291        self._remove = remove
292        # A list of dead weakrefs (keys to be removed)
293        self._pending_removals = []
294        self._iterating = set()
295        if dict is not None:
296            self.update(dict)
297
298    def _commit_removals(self):
299        # NOTE: We don't need to call this method before mutating the dict,
300        # because a dead weakref never compares equal to a live weakref,
301        # even if they happened to refer to equal objects.
302        # However, it means keys may already have been removed.
303        l = self._pending_removals
304        d = self.data
305        while l:
306            try:
307                del d[l.pop()]
308            except KeyError:
309                pass
310
311    def __delitem__(self, key):
312        del self.data[ref(key)]
313
314    def __getitem__(self, key):
315        return self.data[ref(key)]
316
317    def __repr__(self):
318        return "<WeakKeyDictionary at %s>" % id(self)
319
320    def __setitem__(self, key, value):
321        self.data[ref(key, self._remove)] = value
322
323    def copy(self):
324        new = WeakKeyDictionary()
325        for key, value in self.data.items():
326            o = key()
327            if o is not None:
328                new[o] = value
329        return new
330
331    __copy__ = copy
332
333    def __deepcopy__(self, memo):
334        from copy import deepcopy
335        new = self.__class__()
336        for key, value in self.data.items():
337            o = key()
338            if o is not None:
339                new[o] = deepcopy(value, memo)
340        return new
341
342    def get(self, key, default=None):
343        return self.data.get(ref(key),default)
344
345    def has_key(self, key):
346        try:
347            wr = ref(key)
348        except TypeError:
349            return 0
350        return wr in self.data
351
352    def __contains__(self, key):
353        try:
354            wr = ref(key)
355        except TypeError:
356            return 0
357        return wr in self.data
358
359    def items(self):
360        L = []
361        for key, value in self.data.items():
362            o = key()
363            if o is not None:
364                L.append((o, value))
365        return L
366
367    def iteritems(self):
368        with _IterationGuard(self):
369            for wr, value in self.data.iteritems():
370                key = wr()
371                if key is not None:
372                    yield key, value
373
374    def iterkeyrefs(self):
375        """Return an iterator that yields the weak references to the keys.
376
377        The references are not guaranteed to be 'live' at the time
378        they are used, so the result of calling the references needs
379        to be checked before being used.  This can be used to avoid
380        creating references that will cause the garbage collector to
381        keep the keys around longer than needed.
382
383        """
384        with _IterationGuard(self):
385            for wr in self.data.iterkeys():
386                yield wr
387
388    def iterkeys(self):
389        with _IterationGuard(self):
390            for wr in self.data.iterkeys():
391                obj = wr()
392                if obj is not None:
393                    yield obj
394
395    __iter__ = iterkeys
396
397    def itervalues(self):
398        with _IterationGuard(self):
399            for value in self.data.itervalues():
400                yield value
401
402    def keyrefs(self):
403        """Return a list of weak references to the keys.
404
405        The references are not guaranteed to be 'live' at the time
406        they are used, so the result of calling the references needs
407        to be checked before being used.  This can be used to avoid
408        creating references that will cause the garbage collector to
409        keep the keys around longer than needed.
410
411        """
412        return self.data.keys()
413
414    def keys(self):
415        L = []
416        for wr in self.data.keys():
417            o = wr()
418            if o is not None:
419                L.append(o)
420        return L
421
422    def popitem(self):
423        while 1:
424            key, value = self.data.popitem()
425            o = key()
426            if o is not None:
427                return o, value
428
429    def pop(self, key, *args):
430        return self.data.pop(ref(key), *args)
431
432    def setdefault(self, key, default=None):
433        return self.data.setdefault(ref(key, self._remove),default)
434
435    def update(self, dict=None, **kwargs):
436        d = self.data
437        if dict is not None:
438            if not hasattr(dict, "items"):
439                dict = type({})(dict)
440            for key, value in dict.items():
441                d[ref(key, self._remove)] = value
442        if len(kwargs):
443            self.update(kwargs)
444