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__(*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 
    265 class 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 
    286 class 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