blob: b8d804301acb8be304352f9266b81a183f932068 [file] [log] [blame]
Michael Foorde6410c52010-03-29 20:04:23 +00001# 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
6
7__all__ = ['WeakSet']
8
9
10class _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
35class 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 yield item
64
65 def __len__(self):
Antoine Pitrouc56bca32012-03-01 16:26:35 +010066 return len(self.data) - len(self._pending_removals)
Michael Foorde6410c52010-03-29 20:04:23 +000067
68 def __contains__(self, item):
Georg Brandl52f83952011-02-25 10:39:23 +000069 try:
70 wr = ref(item)
71 except TypeError:
72 return False
73 return wr in self.data
Michael Foorde6410c52010-03-29 20:04:23 +000074
75 def __reduce__(self):
76 return (self.__class__, (list(self),),
77 getattr(self, '__dict__', None))
78
79 __hash__ = None
80
81 def add(self, item):
82 if self._pending_removals:
83 self._commit_removals()
84 self.data.add(ref(item, self._remove))
85
86 def clear(self):
87 if self._pending_removals:
88 self._commit_removals()
89 self.data.clear()
90
91 def copy(self):
92 return self.__class__(self)
93
94 def pop(self):
95 if self._pending_removals:
96 self._commit_removals()
97 while True:
98 try:
99 itemref = self.data.pop()
100 except KeyError:
101 raise KeyError('pop from empty WeakSet')
102 item = itemref()
103 if item is not None:
104 return item
105
106 def remove(self, item):
107 if self._pending_removals:
108 self._commit_removals()
109 self.data.remove(ref(item))
110
111 def discard(self, item):
112 if self._pending_removals:
113 self._commit_removals()
114 self.data.discard(ref(item))
115
116 def update(self, other):
117 if self._pending_removals:
118 self._commit_removals()
Antoine Pitrou859416e2012-03-04 20:20:34 +0100119 for element in other:
120 self.add(element)
Michael Foorde6410c52010-03-29 20:04:23 +0000121
122 def __ior__(self, other):
123 self.update(other)
124 return self
125
126 # Helper functions for simple delegating methods.
127 def _apply(self, other, method):
128 if not isinstance(other, self.__class__):
129 other = self.__class__(other)
130 newdata = method(other.data)
131 newset = self.__class__()
132 newset.data = newdata
133 return newset
134
135 def difference(self, other):
136 return self._apply(other, self.data.difference)
137 __sub__ = difference
138
139 def difference_update(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 def __isub__(self, other):
147 if self._pending_removals:
148 self._commit_removals()
149 if self is other:
150 self.data.clear()
151 else:
152 self.data.difference_update(ref(item) for item in other)
153 return self
154
155 def intersection(self, other):
156 return self._apply(other, self.data.intersection)
157 __and__ = intersection
158
159 def intersection_update(self, other):
160 if self._pending_removals:
161 self._commit_removals()
162 self.data.intersection_update(ref(item) for item in other)
163 def __iand__(self, other):
164 if self._pending_removals:
165 self._commit_removals()
166 self.data.intersection_update(ref(item) for item in other)
167 return self
168
169 def issubset(self, other):
170 return self.data.issubset(ref(item) for item in other)
171 __lt__ = issubset
172
173 def __le__(self, other):
174 return self.data <= set(ref(item) for item in other)
175
176 def issuperset(self, other):
177 return self.data.issuperset(ref(item) for item in other)
178 __gt__ = issuperset
179
180 def __ge__(self, other):
181 return self.data >= set(ref(item) for item in other)
182
183 def __eq__(self, other):
184 if not isinstance(other, self.__class__):
185 return NotImplemented
186 return self.data == set(ref(item) for item in other)
187
188 def symmetric_difference(self, other):
189 return self._apply(other, self.data.symmetric_difference)
190 __xor__ = symmetric_difference
191
192 def symmetric_difference_update(self, other):
193 if self._pending_removals:
194 self._commit_removals()
195 if self is other:
196 self.data.clear()
197 else:
198 self.data.symmetric_difference_update(ref(item) for item in other)
199 def __ixor__(self, other):
200 if self._pending_removals:
201 self._commit_removals()
202 if self is other:
203 self.data.clear()
204 else:
205 self.data.symmetric_difference_update(ref(item) for item in other)
206 return self
207
208 def union(self, other):
209 return self._apply(other, self.data.union)
210 __or__ = union
211
212 def isdisjoint(self, other):
213 return len(self.intersection(other)) == 0