Home | History | Annotate | Download | only in Lib
      1 # Access WeakSet through the weakref module.
      2 # This code is separated-out because it is needed
      3 # by abc.py to load everything else at startup.
      4 
      5 from _weakref import ref
      6 
      7 __all__ = ['WeakSet']
      8 
      9 
     10 class _IterationGuard(object):
     11     # This context manager registers itself in the current iterators of the
     12     # weak container, such as to delay all removals until the context manager
     13     # exits.
     14     # This technique should be relatively thread-safe (since sets are).
     15 
     16     def __init__(self, weakcontainer):
     17         # Don't create cycles
     18         self.weakcontainer = ref(weakcontainer)
     19 
     20     def __enter__(self):
     21         w = self.weakcontainer()
     22         if w is not None:
     23             w._iterating.add(self)
     24         return self
     25 
     26     def __exit__(self, e, t, b):
     27         w = self.weakcontainer()
     28         if w is not None:
     29             s = w._iterating
     30             s.remove(self)
     31             if not s:
     32                 w._commit_removals()
     33 
     34 
     35 class WeakSet(object):
     36     def __init__(self, data=None):
     37         self.data = set()
     38         def _remove(item, selfref=ref(self)):
     39             self = selfref()
     40             if self is not None:
     41                 if self._iterating:
     42                     self._pending_removals.append(item)
     43                 else:
     44                     self.data.discard(item)
     45         self._remove = _remove
     46         # A list of keys to be removed
     47         self._pending_removals = []
     48         self._iterating = set()
     49         if data is not None:
     50             self.update(data)
     51 
     52     def _commit_removals(self):
     53         l = self._pending_removals
     54         discard = self.data.discard
     55         while l:
     56             discard(l.pop())
     57 
     58     def __iter__(self):
     59         with _IterationGuard(self):
     60             for itemref in self.data:
     61                 item = itemref()
     62                 if item is not None:
     63                     # Caveat: the iterator will keep a strong reference to
     64                     # `item` until it is resumed or closed.
     65                     yield item
     66 
     67     def __len__(self):
     68         return len(self.data) - len(self._pending_removals)
     69 
     70     def __contains__(self, item):
     71         try:
     72             wr = ref(item)
     73         except TypeError:
     74             return False
     75         return wr in self.data
     76 
     77     def __reduce__(self):
     78         return (self.__class__, (list(self),),
     79                 getattr(self, '__dict__', None))
     80 
     81     __hash__ = None
     82 
     83     def add(self, item):
     84         if self._pending_removals:
     85             self._commit_removals()
     86         self.data.add(ref(item, self._remove))
     87 
     88     def clear(self):
     89         if self._pending_removals:
     90             self._commit_removals()
     91         self.data.clear()
     92 
     93     def copy(self):
     94         return self.__class__(self)
     95 
     96     def pop(self):
     97         if self._pending_removals:
     98             self._commit_removals()
     99         while True:
    100             try:
    101                 itemref = self.data.pop()
    102             except KeyError:
    103                 raise KeyError('pop from empty WeakSet')
    104             item = itemref()
    105             if item is not None:
    106                 return item
    107 
    108     def remove(self, item):
    109         if self._pending_removals:
    110             self._commit_removals()
    111         self.data.remove(ref(item))
    112 
    113     def discard(self, item):
    114         if self._pending_removals:
    115             self._commit_removals()
    116         self.data.discard(ref(item))
    117 
    118     def update(self, other):
    119         if self._pending_removals:
    120             self._commit_removals()
    121         for element in other:
    122             self.add(element)
    123 
    124     def __ior__(self, other):
    125         self.update(other)
    126         return self
    127 
    128     def difference(self, other):
    129         newset = self.copy()
    130         newset.difference_update(other)
    131         return newset
    132     __sub__ = difference
    133 
    134     def difference_update(self, other):
    135         self.__isub__(other)
    136     def __isub__(self, other):
    137         if self._pending_removals:
    138             self._commit_removals()
    139         if self is other:
    140             self.data.clear()
    141         else:
    142             self.data.difference_update(ref(item) for item in other)
    143         return self
    144 
    145     def intersection(self, other):
    146         return self.__class__(item for item in other if item in self)
    147     __and__ = intersection
    148 
    149     def intersection_update(self, other):
    150         self.__iand__(other)
    151     def __iand__(self, other):
    152         if self._pending_removals:
    153             self._commit_removals()
    154         self.data.intersection_update(ref(item) for item in other)
    155         return self
    156 
    157     def issubset(self, other):
    158         return self.data.issubset(ref(item) for item in other)
    159     __le__ = issubset
    160 
    161     def __lt__(self, other):
    162         return self.data < set(ref(item) for item in other)
    163 
    164     def issuperset(self, other):
    165         return self.data.issuperset(ref(item) for item in other)
    166     __ge__ = issuperset
    167 
    168     def __gt__(self, other):
    169         return self.data > set(ref(item) for item in other)
    170 
    171     def __eq__(self, other):
    172         if not isinstance(other, self.__class__):
    173             return NotImplemented
    174         return self.data == set(ref(item) for item in other)
    175 
    176     def __ne__(self, other):
    177         opposite = self.__eq__(other)
    178         if opposite is NotImplemented:
    179             return NotImplemented
    180         return not opposite
    181 
    182     def symmetric_difference(self, other):
    183         newset = self.copy()
    184         newset.symmetric_difference_update(other)
    185         return newset
    186     __xor__ = symmetric_difference
    187 
    188     def symmetric_difference_update(self, other):
    189         self.__ixor__(other)
    190     def __ixor__(self, other):
    191         if self._pending_removals:
    192             self._commit_removals()
    193         if self is other:
    194             self.data.clear()
    195         else:
    196             self.data.symmetric_difference_update(ref(item, self._remove) for item in other)
    197         return self
    198 
    199     def union(self, other):
    200         return self.__class__(e for s in (self, other) for e in s)
    201     __or__ = union
    202 
    203     def isdisjoint(self, other):
    204         return len(self.intersection(other)) == 0
    205