| 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
 |