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