#!/usr/bin/env python
##############################################################################
#
# diffpy.srfit Complex Modeling Initiative
# (c) 2016 Brookhaven Science Associates,
# Brookhaven National Laboratory.
# All rights reserved.
#
# File coded by: Pavol Juhas
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
"""Picklable storage of callable objects using weak references."""
import types
import weakref
[docs]
class WeakBoundMethod(object):
"""Callable wrapper to a bound method stored as a weak reference.
This stores a bound method without keeping its object alive, and can
call a fallback function once that object has been deleted.
Attributes
----------
function : FunctionType
The unbound function extracted from the wrapped bound method.
fallback : FunctionType or None
The plain function called when the object holding the bound method
has been deleted. It is called with this wrapper as the first
argument, followed by any positional and keyword arguments passed
for the bound method, and can be used to deregister this wrapper.
_wref : weakref.ref
The weak reference to the object the wrapped method is bound to.
_class : type
The type of the object the method is bound to. This is only used
for pickling.
"""
__slots__ = ("function", "fallback", "_wref", "_class")
def __init__(self, f, fallback=None):
"""Create a weak reference wrapper to a bound method.
Parameters
----------
f : MethodType
The instance-bound method to wrap.
fallback : FunctionType, optional
The plain function called instead of the bound method once the
method's object has been deleted. Default is None.
"""
# This does not handle builtin methods, but that can be added
# if necessary.
self.function = f.__func__
self.fallback = fallback
self._class = type(f.__self__)
self._wref = weakref.ref(f.__self__)
return
def __call__(self, *args, **kwargs):
"""Call the wrapped method if its object is still alive.
If that object has been deleted and a fallback function is set,
call the fallback function instead.
Parameters
----------
*args, **kwargs
The arguments passed to the wrapped bound method.
Returns
-------
object
The return value of the bound method, or of the fallback
function once the object has been deleted.
Raises
------
ReferenceError
If the method's object has been deleted and no fallback
function is set.
"""
mobj = self._wref()
if mobj is not None:
return self.function(mobj, *args, **kwargs)
if self.fallback is not None:
return self.fallback(self, *args, **kwargs)
emsg = "Object bound to {} does not exist.".format(self.function)
raise ReferenceError(emsg)
# support use of this class in hashed collections
def __hash__(self):
return hash((self.function, self._wref))
def __eq__(self, other):
rv = self.function == other.function and (
self._wref == other._wref or None is self._wref() is other._wref()
)
return rv
def __ne__(self, other):
return not self.__eq__(other)
# support pickling of this type
def __getstate__(self):
"""Return state with a resolved weak reference."""
mobj = self._wref()
nm = self.function.__name__
amsg = "Unable to pickle this unbound function by name."
assert self.function is getattr(self._class, nm), amsg
state = (self._class, nm, self.fallback, mobj)
return state
def __setstate__(self, state):
"""Restore the weak reference in this wrapper upon
unpickling."""
self._class, nm, self.fallback, mobj = state
self.function = getattr(self._class, nm)
if mobj is None:
# use a fake weak reference that mimics deallocated object.
self._wref = self.__mimic_empty_ref
return
# Here the referred object exists.
self._wref = weakref.ref(mobj)
return
@staticmethod
def __mimic_empty_ref():
return None
# end of class WeakBoundMethod
[docs]
def weak_ref(f, fallback=None):
"""Create weak-reference wrapper to a bound method.
Parameters
----------
f : callable
The object-bound method or plain function to wrap.
fallback : FunctionType, optional
The plain function called when the object holding ``f`` has been
deleted. It is called with the wrapper as the first argument,
followed by the positional and keyword arguments passed for the
bound method, and can be used to deregister the wrapper. Default
is None.
Returns
-------
WeakBoundMethod or callable
The weak wrapper when ``f`` is a bound method. When ``f`` is a
plain function or already a WeakBoundMethod, ``f`` itself, and
``fallback`` is ignored.
"""
# NOTE Weak referencing plain functions is probably not needed,
# because they are already bound to the defining modules.
rv = f
if isinstance(f, (types.MethodType, types.BuiltinMethodType)):
rv = WeakBoundMethod(f, fallback=fallback)
return rv