Home | History | Annotate | Download | only in Lib
      1 """Weak reference support for Python.
      2 
      3 This module is an implementation of PEP 205:
      4 
      5 http://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 
     12 import UserDict
     13 
     14 from _weakref import (
     15      getweakrefcount,
     16      getweakrefs,
     17      ref,
     18      proxy,
     19      CallableProxyType,
     20      ProxyType,
     21      ReferenceType)
     22 
     23 from _weakrefset import WeakSet, _IterationGuard
     24 
     25 from exceptions import ReferenceError
     26 
     27 
     28 ProxyTypes = (ProxyType, CallableProxyType)
     29 
     30 __all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
     31            "WeakKeyDictionary", "ReferenceError", "ReferenceType", "ProxyType",
     32            "CallableProxyType", "ProxyTypes", "WeakValueDictionary", 'WeakSet']
     33 
     34 
     35 class 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 
    250 class 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 
    271 class 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