summaryrefslogtreecommitdiff
path: root/silx/utils/weakref.py
diff options
context:
space:
mode:
Diffstat (limited to 'silx/utils/weakref.py')
-rw-r--r--silx/utils/weakref.py361
1 files changed, 361 insertions, 0 deletions
diff --git a/silx/utils/weakref.py b/silx/utils/weakref.py
new file mode 100644
index 0000000..42d7392
--- /dev/null
+++ b/silx/utils/weakref.py
@@ -0,0 +1,361 @@
+# coding: utf-8
+# /*##########################################################################
+#
+# Copyright (c) 2016 European Synchrotron Radiation Facility
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+# ###########################################################################*/
+"""Weakref utils for compatibility between Python 2 and Python 3 or for
+extended features.
+"""
+from __future__ import absolute_import
+
+__authors__ = ["V. Valls"]
+__license__ = "MIT"
+__date__ = "15/09/2016"
+
+
+import weakref
+import types
+import inspect
+
+
+def ref(object, callback=None):
+ """Returns a weak reference to object. The original object can be retrieved
+ by calling the reference object if the referent is still alive. If the
+ referent is no longer alive, calling the reference object will cause None
+ to be returned.
+
+ The signature is the same as the standard `weakref` library, but it returns
+ `WeakMethod` if the object is a bound method.
+
+ :param object: An object
+ :param func callback: If provided, and the returned weakref object is
+ still alive, the callback will be called when the object is about to
+ be finalized. The weak reference object will be passed as the only
+ parameter to the callback. Then the referent will no longer be
+ available.
+ :return: A weak reference to the object
+ """
+ if inspect.ismethod(object):
+ return WeakMethod(object, callback)
+ else:
+ return weakref.ref(object, callback)
+
+
+def proxy(object, callback=None):
+ """Return a proxy to object which uses a weak reference. This supports use
+ of the proxy in most contexts instead of requiring the explicit
+ dereferencing used with weak reference objects.
+
+ The signature is the same as the standard `weakref` library, but it returns
+ `WeakMethodProxy` if the object is a bound method.
+
+ :param object: An object
+ :param func callback: If provided, and the returned weakref object is
+ still alive, the callback will be called when the object is about to
+ be finalized. The weak reference object will be passed as the only
+ parameter to the callback. Then the referent will no longer be
+ available.
+ :return: A proxy to a weak reference of the object
+ """
+ if inspect.ismethod(object):
+ return WeakMethodProxy(object, callback)
+ else:
+ return weakref.proxy(object, callback)
+
+
+class WeakMethod(object):
+ """Wraps a callable object like a function or a bound method.
+ Feature callback when the object is about to be finalized.
+ Provids the same interface as a normal weak reference.
+ """
+
+ def __init__(self, function, callback=None):
+ """
+ Constructor
+ :param function: Function/method to be called
+ :param callback: If callback is provided and not None,
+ and the returned weakref object is still alive, the
+ callback will be called when the object is about to
+ be finalized; the weak reference object will be passed
+ as the only parameter to the callback; the referent will
+ no longer be available
+ """
+ self.__callback = callback
+
+ if inspect.ismethod(function):
+ # it is a bound method
+ self.__obj = weakref.ref(function.__self__, self.__call_callback)
+ self.__method = weakref.ref(function.__func__, self.__call_callback)
+ else:
+ self.__obj = None
+ self.__method = weakref.ref(function, self.__call_callback)
+
+ def __call_callback(self, ref):
+ """Called when the object is about to be finalized"""
+ if not self.is_alive():
+ return
+ self.__obj = None
+ self.__method = None
+ if self.__callback is not None:
+ self.__callback(self)
+
+ def __call__(self):
+ """Return a callable function or None if the WeakMethod is dead."""
+ if self.__obj is not None:
+ method = self.__method()
+ obj = self.__obj()
+ if method is None or obj is None:
+ return None
+ return types.MethodType(method, obj)
+ elif self.__method is not None:
+ return self.__method()
+ else:
+ return None
+
+ def is_alive(self):
+ """True if the WeakMethod is still alive"""
+ return self.__method is not None
+
+ def __eq__(self, other):
+ """Check it another obect is equal to this.
+
+ :param object other: Object to compare with
+ """
+ if isinstance(other, WeakMethod):
+ if not self.is_alive():
+ return False
+ return self.__obj == other.__obj and self.__method == other.__method
+ return False
+
+ def __ne__(self, other):
+ """Check it another obect is not equal to this.
+
+ :param object other: Object to compare with
+ """
+ if isinstance(other, WeakMethod):
+ if not self.is_alive():
+ return False
+ return self.__obj != other.__obj or self.__method != other.__method
+ return True
+
+ def __hash__(self):
+ """Returns the hash for the object."""
+ return self.__obj.__hash__() ^ self.__method.__hash__()
+
+
+class WeakMethodProxy(WeakMethod):
+ """Wraps a callable object like a function or a bound method
+ with a weakref proxy.
+ """
+ def __call__(self, *args, **kwargs):
+ """Dereference the method and call it if the method is still alive.
+ Else raises an ReferenceError.
+
+ :raises: ReferenceError, if the method is not alive
+ """
+ fn = super(WeakMethodProxy, self).__call__()
+ if fn is None:
+ raise ReferenceError("weakly-referenced object no longer exists")
+ return fn(*args, **kwargs)
+
+
+class WeakList(list):
+ """Manage a list of weaked references.
+ When an object is dead, the list is flaged as invalid.
+ If expected the list is cleaned up to remove dead objects.
+ """
+
+ def __init__(self, enumerator=()):
+ """Create a WeakList
+
+ :param iterator enumerator: A list of object to initialize the
+ list
+ """
+ list.__init__(self)
+ self.__list = []
+ self.__is_valid = True
+ for obj in enumerator:
+ self.append(obj)
+
+ def __invalidate(self, ref):
+ """Flag the list as invalidated. The list contains dead references."""
+ self.__is_valid = False
+
+ def __create_ref(self, obj):
+ """Create a weakref from an object. It uses the `ref` module function.
+ """
+ return ref(obj, self.__invalidate)
+
+ def __clean(self):
+ """Clean the list from dead references"""
+ if self.__is_valid:
+ return
+ self.__list = [ref for ref in self.__list if ref() is not None]
+ self.__is_valid = True
+
+ def __iter__(self):
+ """Iterate over objects of the list"""
+ for ref in self.__list:
+ obj = ref()
+ if obj is not None:
+ yield obj
+
+ def __len__(self):
+ """Count item on the list"""
+ self.__clean()
+ return len(self.__list)
+
+ def __getitem__(self, key):
+ """Returns the object at the requested index
+
+ :param key: Indexes to get
+ :type key: int or slice
+ """
+ self.__clean()
+ data = self.__list[key]
+ if isinstance(data, list):
+ result = [ref() for ref in data]
+ else:
+ result = data()
+ return result
+
+ def __setitem__(self, key, obj):
+ """Set an item at an index
+
+ :param key: Indexes to set
+ :type key: int or slice
+ """
+ self.__clean()
+ if isinstance(key, slice):
+ objs = [self.__create_ref(o) for o in obj]
+ self.__list[key] = objs
+ else:
+ obj_ref = self.__create_ref(obj)
+ self.__list[key] = obj_ref
+
+ def __delitem__(self, key):
+ """Delete an Indexes item of this list
+
+ :param key: Index to delete
+ :type key: int or slice
+ """
+ self.__clean()
+ del self.__list[key]
+
+ def __delslice__(self, i, j):
+ """Looks to be used in Python 2.7"""
+ self.__delitem__(slice(i, j, None))
+
+ def __setslice__(self, i, j, sequence):
+ """Looks to be used in Python 2.7"""
+ self.__setitem__(slice(i, j, None), sequence)
+
+ def __getslice__(self, i, j):
+ """Looks to be used in Python 2.7"""
+ return self.__getitem__(slice(i, j, None))
+
+ def __reversed__(self):
+ """Returns a copy of the reverted list"""
+ reversed_list = reversed(list(self))
+ return WeakList(reversed_list)
+
+ def __contains__(self, obj):
+ """Returns true if the object is in the list"""
+ ref = self.__create_ref(obj)
+ return ref in self.__list
+
+ def __add__(self, other):
+ """Returns a WeakList containing this list an the other"""
+ l = WeakList(self)
+ l.extend(other)
+ return l
+
+ def __iadd__(self, other):
+ """Add objects to this list inplace"""
+ self.extend(other)
+ return self
+
+ def __mul__(self, n):
+ """Returns a WeakList containing n-duplication object of this list"""
+ return WeakList(list(self) * n)
+
+ def __imul__(self, n):
+ """N-duplication of the objects to this list inplace"""
+ self.__list *= n
+ return self
+
+ def append(self, obj):
+ """Add an object at the end of the list"""
+ ref = self.__create_ref(obj)
+ self.__list.append(ref)
+
+ def count(self, obj):
+ """Returns the number of occurencies of an object"""
+ ref = self.__create_ref(obj)
+ return self.__list.count(ref)
+
+ def extend(self, other):
+ """Append the list with all objects from another list"""
+ for obj in other:
+ self.append(obj)
+
+ def index(self, obj):
+ """Returns the index of an object"""
+ ref = self.__create_ref(obj)
+ return self.__list.index(ref)
+
+ def insert(self, index, obj):
+ """Insert an object at the requested index"""
+ ref = self.__create_ref(obj)
+ self.__list.insert(index, ref)
+
+ def pop(self, index):
+ """Remove and return an object at the requested index"""
+ self.__clean()
+ obj = self.__list.pop(index)()
+ return obj
+
+ def remove(self, obj):
+ """Remove an object from the list"""
+ ref = self.__create_ref(obj)
+ self.__list.remove(ref)
+
+ def reverse(self):
+ """Reverse the list inplace"""
+ self.__list.reverse()
+
+ def sort(self, key=None, reverse=False):
+ """Sort the list inplace.
+ Not very efficient.
+ """
+ sorted_list = list(self)
+ sorted_list.sort(key=key, reverse=reverse)
+ self.__list = []
+ self.extend(sorted_list)
+
+ def __str__(self):
+ unref_list = list(self)
+ return "WeakList(%s)" % str(unref_list)
+
+ def __repr__(self):
+ unref_list = list(self)
+ return "WeakList(%s)" % repr(unref_list)