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