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
|