weakref.py 11.2 KB
Newer Older
1 2 3 4
"""Weak reference support for Python.

This module is an implementation of PEP 205:

Christian Heimes's avatar
Christian Heimes committed
5
http://www.python.org/dev/peps/pep-0205/
6 7
"""

8 9 10 11
# Naming convention: Variables named "wr" are weak reference objects;
# they are called this instead of "ref" to avoid name collisions with
# the module-global ref() function imported from _weakref.

12 13 14 15 16 17 18 19
from _weakref import (
     getweakrefcount,
     getweakrefs,
     ref,
     proxy,
     CallableProxyType,
     ProxyType,
     ReferenceType)
20

21
from _weakrefset import WeakSet, _IterationGuard
22

23 24
import collections  # Import after _weakref to avoid circular import.

25 26
ProxyTypes = (ProxyType, CallableProxyType)

27
__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
28
           "WeakKeyDictionary", "ReferenceType", "ProxyType",
29 30
           "CallableProxyType", "ProxyTypes", "WeakValueDictionary",
           "WeakSet"]
31

32

33
class WeakValueDictionary(collections.MutableMapping):
34
    """Mapping class that references values weakly.
35

36 37 38
    Entries in the dictionary will be discarded when no strong
    reference to the value exists anymore
    """
39 40
    # We inherit the constructor without worrying about the input
    # dictionary; since it uses our .update() method, we get the right
41 42 43
    # checks (if the other dictionary is a WeakValueDictionary,
    # objects are unwrapped on the way out, and we always wrap on the
    # way in).
44

45 46 47 48
    def __init__(self, *args, **kw):
        def remove(wr, selfref=ref(self)):
            self = selfref()
            if self is not None:
49 50 51 52
                if self._iterating:
                    self._pending_removals.append(wr.key)
                else:
                    del self.data[wr.key]
53
        self._remove = remove
54 55 56
        # A list of keys to be removed
        self._pending_removals = []
        self._iterating = set()
57
        self.data = d = {}
58
        self.update(*args, **kw)
59

60 61 62 63 64 65 66 67
    def _commit_removals(self):
        l = self._pending_removals
        d = self.data
        # We shouldn't encounter any KeyError, because this method should
        # always be called *before* mutating the dict.
        while l:
            del d[l.pop()]

68
    def __getitem__(self, key):
69
        o = self.data[key]()
70
        if o is None:
71
            raise KeyError(key)
72 73 74
        else:
            return o

75
    def __delitem__(self, key):
76 77
        if self._pending_removals:
            self._commit_removals()
78 79 80
        del self.data[key]

    def __len__(self):
81
        return len(self.data) - len(self._pending_removals)
82

83 84 85 86 87 88 89
    def __contains__(self, key):
        try:
            o = self.data[key]()
        except KeyError:
            return False
        return o is not None

90
    def __repr__(self):
91
        return "<WeakValueDictionary at %s>" % id(self)
92 93

    def __setitem__(self, key, value):
94 95
        if self._pending_removals:
            self._commit_removals()
96
        self.data[key] = KeyedRef(value, self._remove, key)
97 98

    def copy(self):
99
        new = WeakValueDictionary()
100 101
        for key, wr in self.data.items():
            o = wr()
102 103
            if o is not None:
                new[key] = o
104
        return new
105

106 107 108 109 110 111 112 113 114 115 116
    __copy__ = copy

    def __deepcopy__(self, memo):
        from copy import deepcopy
        new = self.__class__()
        for key, wr in self.data.items():
            o = wr()
            if o is not None:
                new[deepcopy(key, memo)] = o
        return new

117
    def get(self, key, default=None):
118
        try:
119
            wr = self.data[key]
120 121 122
        except KeyError:
            return default
        else:
123
            o = wr()
124 125 126 127 128 129 130
            if o is None:
                # This should only happen
                return default
            else:
                return o

    def items(self):
131 132 133 134 135
        with _IterationGuard(self):
            for k, wr in self.data.items():
                v = wr()
                if v is not None:
                    yield k, v
136

137
    def keys(self):
138 139 140 141
        with _IterationGuard(self):
            for k, wr in self.data.items():
                if wr() is not None:
                    yield k
142

143
    __iter__ = keys
144

145 146 147 148 149 150 151 152 153 154
    def itervaluerefs(self):
        """Return an iterator that yields the weak references to the values.

        The references are not guaranteed to be 'live' at the time
        they are used, so the result of calling the references needs
        to be checked before being used.  This can be used to avoid
        creating references that will cause the garbage collector to
        keep the values around longer than needed.

        """
155 156 157
        with _IterationGuard(self):
            for wr in self.data.values():
                yield wr
158

159
    def values(self):
160 161 162 163 164
        with _IterationGuard(self):
            for wr in self.data.values():
                obj = wr()
                if obj is not None:
                    yield obj
165

166
    def popitem(self):
167 168
        if self._pending_removals:
            self._commit_removals()
Georg Brandl's avatar
Georg Brandl committed
169
        while True:
170 171
            key, wr = self.data.popitem()
            o = wr()
172 173 174
            if o is not None:
                return key, o

175
    def pop(self, key, *args):
176 177
        if self._pending_removals:
            self._commit_removals()
178 179 180 181 182 183 184
        try:
            o = self.data.pop(key)()
        except KeyError:
            if args:
                return args[0]
            raise
        if o is None:
185
            raise KeyError(key)
186 187 188
        else:
            return o

189
    def setdefault(self, key, default=None):
190
        try:
191
            wr = self.data[key]
192
        except KeyError:
193 194
            if self._pending_removals:
                self._commit_removals()
195
            self.data[key] = KeyedRef(default, self._remove, key)
196 197
            return default
        else:
198
            return wr()
199

200
    def update(self, dict=None, **kwargs):
201 202
        if self._pending_removals:
            self._commit_removals()
203
        d = self.data
204 205 206 207
        if dict is not None:
            if not hasattr(dict, "items"):
                dict = type({})(dict)
            for key, o in dict.items():
208
                d[key] = KeyedRef(o, self._remove, key)
209 210
        if len(kwargs):
            self.update(kwargs)
211

212 213 214 215 216 217 218 219 220 221
    def valuerefs(self):
        """Return a list of weak references to the values.

        The references are not guaranteed to be 'live' at the time
        they are used, so the result of calling the references needs
        to be checked before being used.  This can be used to avoid
        creating references that will cause the garbage collector to
        keep the values around longer than needed.

        """
222
        return list(self.data.values())
223

224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242

class KeyedRef(ref):
    """Specialized reference that includes a key corresponding to the value.

    This is used in the WeakValueDictionary to avoid having to create
    a function object for each key stored in the mapping.  A shared
    callback object can use the 'key' attribute of a KeyedRef instead
    of getting a reference to the key from an enclosing scope.

    """

    __slots__ = "key",

    def __new__(type, ob, callback, key):
        self = ref.__new__(type, ob, callback)
        self.key = key
        return self

    def __init__(self, ob, callback, key):
243
        super().__init__(ob, callback)
244

245

246
class WeakKeyDictionary(collections.MutableMapping):
247 248 249 250 251 252 253 254 255
    """ Mapping class that references keys weakly.

    Entries in the dictionary will be discarded when there is no
    longer a strong reference to the key. This can be used to
    associate additional data with an object owned by other parts of
    an application without adding attributes to those objects. This
    can be especially useful with objects that override attribute
    accesses.
    """
256 257 258

    def __init__(self, dict=None):
        self.data = {}
259 260 261
        def remove(k, selfref=ref(self)):
            self = selfref()
            if self is not None:
262 263 264 265
                if self._iterating:
                    self._pending_removals.append(k)
                else:
                    del self.data[k]
266
        self._remove = remove
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
        # A list of dead weakrefs (keys to be removed)
        self._pending_removals = []
        self._iterating = set()
        if dict is not None:
            self.update(dict)

    def _commit_removals(self):
        # NOTE: We don't need to call this method before mutating the dict,
        # because a dead weakref never compares equal to a live weakref,
        # even if they happened to refer to equal objects.
        # However, it means keys may already have been removed.
        l = self._pending_removals
        d = self.data
        while l:
            try:
                del d[l.pop()]
            except KeyError:
                pass
285

286
    def __delitem__(self, key):
287
        del self.data[ref(key)]
288

289 290 291
    def __getitem__(self, key):
        return self.data[ref(key)]

292
    def __len__(self):
293
        return len(self.data) - len(self._pending_removals)
294

295 296 297 298 299 300 301 302 303 304 305 306
    def __repr__(self):
        return "<WeakKeyDictionary at %s>" % id(self)

    def __setitem__(self, key, value):
        self.data[ref(key, self._remove)] = value

    def copy(self):
        new = WeakKeyDictionary()
        for key, value in self.data.items():
            o = key()
            if o is not None:
                new[o] = value
307
        return new
308

309 310 311 312 313 314 315 316 317 318 319
    __copy__ = copy

    def __deepcopy__(self, memo):
        from copy import deepcopy
        new = self.__class__()
        for key, value in self.data.items():
            o = key()
            if o is not None:
                new[o] = deepcopy(value, memo)
        return new

320
    def get(self, key, default=None):
321 322
        return self.data.get(ref(key),default)

323 324 325 326
    def __contains__(self, key):
        try:
            wr = ref(key)
        except TypeError:
Georg Brandl's avatar
Georg Brandl committed
327
            return False
328
        return wr in self.data
Tim Peters's avatar
Tim Peters committed
329

330
    def items(self):
331 332 333 334 335
        with _IterationGuard(self):
            for wr, value in self.data.items():
                key = wr()
                if key is not None:
                    yield key, value
336

337
    def keys(self):
338 339 340 341 342
        with _IterationGuard(self):
            for wr in self.data:
                obj = wr()
                if obj is not None:
                    yield obj
343

344
    __iter__ = keys
345

346
    def values(self):
347 348 349 350
        with _IterationGuard(self):
            for wr, value in self.data.items():
                if wr() is not None:
                    yield value
351

352 353 354 355 356 357 358 359 360 361
    def keyrefs(self):
        """Return a list of weak references to the keys.

        The references are not guaranteed to be 'live' at the time
        they are used, so the result of calling the references needs
        to be checked before being used.  This can be used to avoid
        creating references that will cause the garbage collector to
        keep the keys around longer than needed.

        """
362
        return list(self.data)
363

364
    def popitem(self):
Georg Brandl's avatar
Georg Brandl committed
365
        while True:
366 367 368 369 370
            key, value = self.data.popitem()
            o = key()
            if o is not None:
                return o, value

371 372 373
    def pop(self, key, *args):
        return self.data.pop(ref(key), *args)

374
    def setdefault(self, key, default=None):
375 376
        return self.data.setdefault(ref(key, self._remove),default)

377
    def update(self, dict=None, **kwargs):
378
        d = self.data
379 380 381 382 383 384 385
        if dict is not None:
            if not hasattr(dict, "items"):
                dict = type({})(dict)
            for key, value in dict.items():
                d[ref(key, self._remove)] = value
        if len(kwargs):
            self.update(kwargs)