# Access WeakSet through the weakref module. | |
# This code is separated-out because it is needed | |
# by abc.py to load everything else at startup. | |
from _weakref import ref | |
__all__ = ['WeakSet'] | |
class _IterationGuard(object): | |
# This context manager registers itself in the current iterators of the | |
# weak container, such as to delay all removals until the context manager | |
# exits. | |
# This technique should be relatively thread-safe (since sets are). | |
def __init__(self, weakcontainer): | |
# Don't create cycles | |
self.weakcontainer = ref(weakcontainer) | |
def __enter__(self): | |
w = self.weakcontainer() | |
if w is not None: | |
w._iterating.add(self) | |
return self | |
def __exit__(self, e, t, b): | |
w = self.weakcontainer() | |
if w is not None: | |
s = w._iterating | |
s.remove(self) | |
if not s: | |
w._commit_removals() | |
class WeakSet(object): | |
def __init__(self, data=None): | |
self.data = set() | |
def _remove(item, selfref=ref(self)): | |
self = selfref() | |
if self is not None: | |
if self._iterating: | |
self._pending_removals.append(item) | |
else: | |
self.data.discard(item) | |
self._remove = _remove | |
# A list of keys to be removed | |
self._pending_removals = [] | |
self._iterating = set() | |
if data is not None: | |
self.update(data) | |
def _commit_removals(self): | |
l = self._pending_removals | |
discard = self.data.discard | |
while l: | |
discard(l.pop()) | |
def __iter__(self): | |
with _IterationGuard(self): | |
for itemref in self.data: | |
item = itemref() | |
if item is not None: | |
yield item | |
def __len__(self): | |
return sum(x() is not None for x in self.data) | |
def __contains__(self, item): | |
try: | |
wr = ref(item) | |
except TypeError: | |
return False | |
return wr in self.data | |
def __reduce__(self): | |
return (self.__class__, (list(self),), | |
getattr(self, '__dict__', None)) | |
__hash__ = None | |
def add(self, item): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.add(ref(item, self._remove)) | |
def clear(self): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.clear() | |
def copy(self): | |
return self.__class__(self) | |
def pop(self): | |
if self._pending_removals: | |
self._commit_removals() | |
while True: | |
try: | |
itemref = self.data.pop() | |
except KeyError: | |
raise KeyError('pop from empty WeakSet') | |
item = itemref() | |
if item is not None: | |
return item | |
def remove(self, item): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.remove(ref(item)) | |
def discard(self, item): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.discard(ref(item)) | |
def update(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
if isinstance(other, self.__class__): | |
self.data.update(other.data) | |
else: | |
for element in other: | |
self.add(element) | |
def __ior__(self, other): | |
self.update(other) | |
return self | |
# Helper functions for simple delegating methods. | |
def _apply(self, other, method): | |
if not isinstance(other, self.__class__): | |
other = self.__class__(other) | |
newdata = method(other.data) | |
newset = self.__class__() | |
newset.data = newdata | |
return newset | |
def difference(self, other): | |
return self._apply(other, self.data.difference) | |
__sub__ = difference | |
def difference_update(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
if self is other: | |
self.data.clear() | |
else: | |
self.data.difference_update(ref(item) for item in other) | |
def __isub__(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
if self is other: | |
self.data.clear() | |
else: | |
self.data.difference_update(ref(item) for item in other) | |
return self | |
def intersection(self, other): | |
return self._apply(other, self.data.intersection) | |
__and__ = intersection | |
def intersection_update(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.intersection_update(ref(item) for item in other) | |
def __iand__(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
self.data.intersection_update(ref(item) for item in other) | |
return self | |
def issubset(self, other): | |
return self.data.issubset(ref(item) for item in other) | |
__lt__ = issubset | |
def __le__(self, other): | |
return self.data <= set(ref(item) for item in other) | |
def issuperset(self, other): | |
return self.data.issuperset(ref(item) for item in other) | |
__gt__ = issuperset | |
def __ge__(self, other): | |
return self.data >= set(ref(item) for item in other) | |
def __eq__(self, other): | |
if not isinstance(other, self.__class__): | |
return NotImplemented | |
return self.data == set(ref(item) for item in other) | |
def symmetric_difference(self, other): | |
return self._apply(other, self.data.symmetric_difference) | |
__xor__ = symmetric_difference | |
def symmetric_difference_update(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
if self is other: | |
self.data.clear() | |
else: | |
self.data.symmetric_difference_update(ref(item) for item in other) | |
def __ixor__(self, other): | |
if self._pending_removals: | |
self._commit_removals() | |
if self is other: | |
self.data.clear() | |
else: | |
self.data.symmetric_difference_update(ref(item) for item in other) | |
return self | |
def union(self, other): | |
return self._apply(other, self.data.union) | |
__or__ = union | |
def isdisjoint(self, other): | |
return len(self.intersection(other)) == 0 |