diffpy.srfit 3.1.0rc0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. diffpy/__init__.py +15 -0
  2. diffpy/srfit/__init__.py +41 -0
  3. diffpy/srfit/equation/__init__.py +31 -0
  4. diffpy/srfit/equation/builder.py +702 -0
  5. diffpy/srfit/equation/equationmod.py +213 -0
  6. diffpy/srfit/equation/literals/__init__.py +71 -0
  7. diffpy/srfit/equation/literals/abcs.py +81 -0
  8. diffpy/srfit/equation/literals/argument.py +76 -0
  9. diffpy/srfit/equation/literals/literal.py +69 -0
  10. diffpy/srfit/equation/literals/operators.py +380 -0
  11. diffpy/srfit/equation/visitors/__init__.py +94 -0
  12. diffpy/srfit/equation/visitors/argfinder.py +62 -0
  13. diffpy/srfit/equation/visitors/printer.py +120 -0
  14. diffpy/srfit/equation/visitors/swapper.py +142 -0
  15. diffpy/srfit/equation/visitors/validator.py +113 -0
  16. diffpy/srfit/equation/visitors/visitor.py +62 -0
  17. diffpy/srfit/exceptions.py +29 -0
  18. diffpy/srfit/fitbase/__init__.py +53 -0
  19. diffpy/srfit/fitbase/calculator.py +110 -0
  20. diffpy/srfit/fitbase/configurable.py +57 -0
  21. diffpy/srfit/fitbase/constraint.py +117 -0
  22. diffpy/srfit/fitbase/fitcontribution.py +351 -0
  23. diffpy/srfit/fitbase/fithook.py +227 -0
  24. diffpy/srfit/fitbase/fitrecipe.py +826 -0
  25. diffpy/srfit/fitbase/fitresults.py +674 -0
  26. diffpy/srfit/fitbase/parameter.py +314 -0
  27. diffpy/srfit/fitbase/parameterset.py +110 -0
  28. diffpy/srfit/fitbase/profile.py +405 -0
  29. diffpy/srfit/fitbase/profilegenerator.py +168 -0
  30. diffpy/srfit/fitbase/profileparser.py +179 -0
  31. diffpy/srfit/fitbase/recipeorganizer.py +1074 -0
  32. diffpy/srfit/fitbase/restraint.py +117 -0
  33. diffpy/srfit/fitbase/simplerecipe.py +230 -0
  34. diffpy/srfit/fitbase/validatable.py +59 -0
  35. diffpy/srfit/interface/__init__.py +33 -0
  36. diffpy/srfit/interface/interface.py +180 -0
  37. diffpy/srfit/pdf/__init__.py +24 -0
  38. diffpy/srfit/pdf/basepdfgenerator.py +296 -0
  39. diffpy/srfit/pdf/characteristicfunctions.py +432 -0
  40. diffpy/srfit/pdf/debyepdfgenerator.py +115 -0
  41. diffpy/srfit/pdf/pdfcontribution.py +311 -0
  42. diffpy/srfit/pdf/pdfgenerator.py +77 -0
  43. diffpy/srfit/pdf/pdfparser.py +226 -0
  44. diffpy/srfit/sas/__init__.py +30 -0
  45. diffpy/srfit/sas/prcalculator.py +139 -0
  46. diffpy/srfit/sas/sasgenerator.py +70 -0
  47. diffpy/srfit/sas/sasimport.py +66 -0
  48. diffpy/srfit/sas/sasparameter.py +70 -0
  49. diffpy/srfit/sas/sasparser.py +133 -0
  50. diffpy/srfit/sas/sasprofile.py +94 -0
  51. diffpy/srfit/structure/__init__.py +60 -0
  52. diffpy/srfit/structure/basestructureparset.py +62 -0
  53. diffpy/srfit/structure/bvsrestraint.py +101 -0
  54. diffpy/srfit/structure/cctbxparset.py +292 -0
  55. diffpy/srfit/structure/diffpyparset.py +303 -0
  56. diffpy/srfit/structure/objcrystparset.py +1552 -0
  57. diffpy/srfit/structure/sgconstraints.py +761 -0
  58. diffpy/srfit/structure/srrealparset.py +92 -0
  59. diffpy/srfit/util/__init__.py +50 -0
  60. diffpy/srfit/util/argbinders.py +29 -0
  61. diffpy/srfit/util/inpututils.py +50 -0
  62. diffpy/srfit/util/nameutils.py +45 -0
  63. diffpy/srfit/util/observable.py +93 -0
  64. diffpy/srfit/util/tagmanager.py +150 -0
  65. diffpy/srfit/util/weakrefcallable.py +170 -0
  66. diffpy/srfit/version.py +26 -0
  67. diffpy_srfit-3.1.0rc0.dist-info/METADATA +216 -0
  68. diffpy_srfit-3.1.0rc0.dist-info/RECORD +73 -0
  69. diffpy_srfit-3.1.0rc0.dist-info/WHEEL +5 -0
  70. diffpy_srfit-3.1.0rc0.dist-info/licenses/AUTHORS.rst +10 -0
  71. diffpy_srfit-3.1.0rc0.dist-info/licenses/LICENSE.rst +138 -0
  72. diffpy_srfit-3.1.0rc0.dist-info/licenses/LICENSE_DANSE.rst +34 -0
  73. diffpy_srfit-3.1.0rc0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2009 The Trustees of Columbia University
7
+ # in the City of New York. All rights reserved.
8
+ #
9
+ # File coded by: Chris Farrow
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Structure wrapper class for structures compatible with SrReal."""
16
+
17
+ __all__ = ["SrRealParSet"]
18
+
19
+ from diffpy.srfit.structure.basestructureparset import BaseStructureParSet
20
+ from diffpy.srfit.structure.bvsrestraint import BVSRestraint
21
+
22
+
23
+ class SrRealParSet(BaseStructureParSet):
24
+ """Base class for SrReal-compatible structure adapters.
25
+
26
+ This derives from BaseStructureParSet and provides some extended
27
+ functionality provided by SrReal.
28
+
29
+ Attributes:
30
+ stru -- The adapted object
31
+ _usesymmetry -- A flag indicating if SrReal calculators that operate on
32
+ this object should use symmetry. By default this is
33
+ True.
34
+ """
35
+
36
+ def __init__(self, *args, **kw):
37
+ BaseStructureParSet.__init__(self, *args, **kw)
38
+ self._usesymmetry = True
39
+ self.stru = None
40
+ return
41
+
42
+ def restrainBVS(self, sig=1, scaled=False):
43
+ """Restrain the bond-valence sum to zero.
44
+
45
+ This adds a penalty to the cost function equal to
46
+ bvmsdiff / sig**2
47
+ where bvmsdiff is the mean-squared difference between the calculated
48
+ and expected bond valence sums for the structure. If scaled is True,
49
+ this is also scaled by the current point-averaged chi^2 value so the
50
+ restraint is roughly equally weighted in the fit.
51
+
52
+ sig -- The uncertainty on the BVS (default 1).
53
+ scaled -- A flag indicating if the restraint is scaled
54
+ (multiplied) by the unrestrained point-average chi^2
55
+ (chi^2/numpoints) (default False).
56
+
57
+ Returns the BVSRestraint object for use with the 'unrestrain' method.
58
+ """
59
+
60
+ # Create the Restraint object
61
+ res = BVSRestraint(self, sig, scaled)
62
+ # Add it to the _restraints set
63
+ self._restraints.add(res)
64
+ # Our configuration changed. Notify observers.
65
+ self._updateConfiguration()
66
+ # Return the Restraint object
67
+ return res
68
+
69
+ def useSymmetry(self, use=True):
70
+ """Set this structure to use symmetry.
71
+
72
+ This determines how the structure is treated by SrReal
73
+ calculators.
74
+ """
75
+ self._usesymmetry = bool(use)
76
+ return
77
+
78
+ def usingSymmetry(self):
79
+ """Check if symmetry is being used."""
80
+ return self._usesymmetry
81
+
82
+ def _getSrRealStructure(self):
83
+ """Get the structure object for use with SrReal calculators.
84
+
85
+ If this is periodic, then return the structure, otherwise, pass
86
+ it inside of a nosymmetry wrapper.
87
+ """
88
+ from diffpy.srreal.structureadapter import nosymmetry
89
+
90
+ if self._usesymmetry:
91
+ return self.stru
92
+ return nosymmetry(self.stru)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2008 The Trustees of Columbia University
7
+ # in the City of New York. All rights reserved.
8
+ #
9
+ # File coded by: Chris Farrow
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Utilities and constants used throughout SrFit."""
16
+
17
+ _DASHEDLINE = 78 * "-"
18
+
19
+
20
+ def sortKeyForNumericString(s):
21
+ """Compute key for sorting strings according to their integer numeric
22
+ value.
23
+
24
+ Each string gets split to string and integer segments to create keys
25
+ for comparison. Signs, decimal points and exponents are ignored.
26
+ This function is intended as the ``key`` argument for the ``sorted``
27
+ or ``list.sort`` function.
28
+
29
+ Parameters
30
+ ----------
31
+ s : str
32
+ String which may have numeric components, e.g., "a12b".
33
+
34
+ Returns
35
+ -------
36
+ tuple
37
+ Tuple of non-numeric segments intermixed with integer values.
38
+ """
39
+ if sortKeyForNumericString._rx is None:
40
+ import re
41
+
42
+ sortKeyForNumericString._rx = re.compile(r"(\d+)")
43
+ rx = sortKeyForNumericString._rx
44
+ rv = tuple((int(w) if i % 2 else w) for i, w in enumerate(rx.split(s)))
45
+ return rv
46
+
47
+
48
+ sortKeyForNumericString._rx = None
49
+
50
+ # End of file
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit Complex Modeling Initiative
5
+ # (c) 2014 Brookhaven Science Associates,
6
+ # Brookhaven National Laboratory.
7
+ # All rights reserved.
8
+ #
9
+ # File coded by: Pavol Juhas
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Functions for binding arguments of callable objects."""
16
+
17
+
18
+ class bind2nd(object):
19
+ """Freeze second argument of a callable object to a given constant."""
20
+
21
+ def __init__(self, func, arg1):
22
+ """Freeze the second argument of function func to arg1."""
23
+ self.func = func
24
+ self.arg1 = arg1
25
+ return
26
+
27
+ def __call__(self, *args, **kwargs):
28
+ boundargs = (args[0], self.arg1) + args[1:]
29
+ return self.func(*boundargs, **kwargs)
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2010 The Trustees of Columbia University
7
+ # in the City of New York. All rights reserved.
8
+ #
9
+ # File coded by: Chris Farrow
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Input utilities."""
16
+
17
+ __all__ = ["inputToString"]
18
+
19
+ import os.path
20
+
21
+
22
+ def inputToString(input):
23
+ """Convert input from various modes to a string.
24
+
25
+ This is useful when you want a method to accept a string, open file object
26
+ or file name.
27
+
28
+ input -- An open file-like object, name of a file
29
+ or a string containing the input.
30
+
31
+ Returns the input in a string
32
+ Raises IOError if the input is supected to be a file name, but the file
33
+ cannot be found.
34
+ """
35
+ # Get the input into a string
36
+ inptstr = ""
37
+ if hasattr(input, "read"):
38
+ inptstr = input.read()
39
+ # TODO remove handling of string input accept only file or filename
40
+ # FIXME check for typos in the file name
41
+ elif os.path.exists(input) or (len(input) < 80 and input.count("\n") == 0):
42
+ with open(input, "r") as infile:
43
+ inptstr = infile.read()
44
+ else:
45
+ inptstr = input
46
+
47
+ return inptstr
48
+
49
+
50
+ # End of file
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2008 The Trustees of Columbia University
7
+ # in the City of New York. All rights reserved.
8
+ #
9
+ # File coded by: Chris Farrow
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Name utilities."""
16
+
17
+ __all__ = ["isIdentifier", "validateName"]
18
+
19
+ import re
20
+
21
+ reident = re.compile(r"^[a-zA-Z_]\w*$")
22
+
23
+
24
+ def isIdentifier(s):
25
+ """Check to see if a python string is a valid identifier.
26
+
27
+ From http://code.activestate.com/recipes/413487/
28
+ """
29
+ if reident.match(s) is None:
30
+ return False
31
+ return True
32
+
33
+
34
+ def validateName(name):
35
+ """Validate that a name is a valid identifier.
36
+
37
+ Raises ValueError if the name is invalid.
38
+ """
39
+ # Check that the name is valid
40
+ if not isIdentifier(name):
41
+ raise ValueError(f"Name {name} is not a valid identifier")
42
+ return
43
+
44
+
45
+ # End of file
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
+ #
6
+ # michael a.g. aïvázis
7
+ # orthologue
8
+ # (c) 1998-2009 all rights reserved
9
+ #
10
+ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11
+ # Derived from pyre-1.0/packages/pyre/patterns/Observable.py
12
+ # See pyre-1.0 for full copyright and license information
13
+
14
+ __all__ = ["Observable"]
15
+
16
+
17
+ from diffpy.srfit.util.weakrefcallable import weak_ref
18
+
19
+
20
+ class Observable(object):
21
+ """Provide notification support for classes that maintain dynamic
22
+ associations with multiple clients.
23
+
24
+ Observers, i.e. clients of the observable, register event handlers
25
+ that will be invoked to notify them whenever something interesting
26
+ happens to the observable. The nature of what is being observed is
27
+ defined by Observable descendants and their managers. For example,
28
+ instances of pyre.calc.Node are observable by other nodes whose
29
+ value depends on them so that the dependents can be notified about
30
+ value changes and forced to recompute their own value.
31
+
32
+ The event handlers are callables that take the observable instance
33
+ as their single argument.
34
+
35
+ interface:
36
+ addObserver: registers its callable argument with the list of
37
+ handlers to invoke removeObserver: remove an event handler from
38
+ the list of handlers to invoke notify: invoke the registered
39
+ handlers in the order in which they were registered
40
+ """
41
+
42
+ def notify(self, other=()):
43
+ """Notify all observers."""
44
+ # build a list before notification, just in case the observer's
45
+ # callback behavior involves removing itself from our callback set
46
+ semaphores = (self,) + other
47
+ for callable in tuple(self._observers):
48
+ callable(semaphores)
49
+ return
50
+
51
+ # callback management
52
+
53
+ def addObserver(self, callable):
54
+ """Add callable to the set of observers."""
55
+ f = weak_ref(callable, fallback=_fbRemoveObserver)
56
+ self._observers.add(f)
57
+ return
58
+
59
+ def removeObserver(self, callable):
60
+ """Remove callable from the set of observers."""
61
+ f = weak_ref(callable)
62
+ self._observers.remove(f)
63
+ return
64
+
65
+ def hasObserver(self, callable):
66
+ """True if `callable` is present in the set of observers."""
67
+ f = weak_ref(callable)
68
+ rv = f in self._observers
69
+ return rv
70
+
71
+ # meta methods
72
+
73
+ def __init__(self, **kwds):
74
+ super(Observable, self).__init__(**kwds)
75
+ self._observers = set()
76
+ return
77
+
78
+
79
+ # end of class Observable
80
+
81
+ # Local helpers --------------------------------------------------------------
82
+
83
+
84
+ def _fbRemoveObserver(fobs, semaphores):
85
+ # Remove WeakBoundMethod `fobs` from the observers of notifying object.
86
+ # This is called from Observable.notify when the WeakBoundMethod
87
+ # associated object dies.
88
+ observable = semaphores[0]
89
+ observable.removeObserver(fobs)
90
+ return
91
+
92
+
93
+ # end of file
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit by DANSE Diffraction group
5
+ # Simon J. L. Billinge
6
+ # (c) 2008 The Trustees of Columbia University
7
+ # in the City of New York. All rights reserved.
8
+ #
9
+ # File coded by: Chris Farrow
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE_DANSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """TagManager class.
16
+
17
+ The TagManager class takes hashable objects and assigns tags to them.
18
+ Objects can then be easily referenced via their assigned tags.
19
+ """
20
+
21
+ __all__ = ["TagManager"]
22
+
23
+ import functools
24
+
25
+
26
+ class TagManager(object):
27
+ """TagManager class.
28
+
29
+ Manage tags on hashable objects. Tags are strings that carry metadata.
30
+
31
+ silent -- Flag indicating whether to silently pass by when a tag
32
+ cannot be found (bool, True). If this is False, then a
33
+ KeyError will be thrown when a tag cannot be found.
34
+ _tagdict -- A dictionary of tags to sets of tagged objects.
35
+ """
36
+
37
+ def __init__(self):
38
+ """Initialization."""
39
+ self._tagdict = {}
40
+ self.silent = True
41
+ return
42
+
43
+ def alltags(self):
44
+ """Get all tags managed by the TagManager."""
45
+ return self._tagdict.keys()
46
+
47
+ def tag(self, obj, *tags):
48
+ """Tag an object.
49
+
50
+ Tags are stored as strings.
51
+
52
+ obj -- Any hashable object to be untagged.
53
+ *tags -- Tags to apply to obj.
54
+
55
+ Raises TypeError if obj is not hashable.
56
+ """
57
+ for tag in tags:
58
+ oset = self._tagdict.setdefault(str(tag), set())
59
+ oset.add(obj)
60
+ return
61
+
62
+ def untag(self, obj, *tags):
63
+ """Remove tags from an object.
64
+
65
+ obj -- Any hashable object to be untagged.
66
+ *tags -- Tags to remove from obj. If this is empty, then all
67
+ tags will be removed from obj.
68
+
69
+ Raises KeyError if a passed tag does not apply to obj and self.silent
70
+ is False
71
+ """
72
+ if not tags:
73
+ tags = self.tags(obj)
74
+
75
+ for tag in tags:
76
+ oset = self.__getObjectSet(tag)
77
+ if obj not in oset and not self.silent:
78
+ raise KeyError("Tag '%s' does not apply" % tag)
79
+ oset.discard(obj)
80
+
81
+ return
82
+
83
+ def tags(self, obj):
84
+ """Get all tags on an object.
85
+
86
+ Returns list
87
+ """
88
+ tags = [k for (k, v) in self._tagdict.items() if obj in v]
89
+ return tags
90
+
91
+ def hasTags(self, obj, *tags):
92
+ """Determine if an object has all passed tags.
93
+
94
+ Returns bool
95
+ """
96
+ setgen = (self.__getObjectSet(t) for t in tags)
97
+ result = all(obj in s for s in setgen)
98
+ return result
99
+
100
+ def union(self, *tags):
101
+ """Get all objects that have any of the passed tags.
102
+
103
+ Returns set
104
+ """
105
+ if not tags:
106
+ return set()
107
+ setgen = (self.__getObjectSet(t) for t in tags)
108
+ objs = functools.reduce(set.union, setgen)
109
+ return objs
110
+
111
+ def intersection(self, *tags):
112
+ """Get all objects that have all of the passed tags.
113
+
114
+ Returns set
115
+ """
116
+ if not tags:
117
+ return set()
118
+ setgen = (self.__getObjectSet(t) for t in tags)
119
+ objs = functools.reduce(set.intersection, setgen)
120
+ return objs
121
+
122
+ def verifyTags(self, *tags):
123
+ """Check that tags are all extant.
124
+
125
+ Raises KeyError if a passed tag does not exist. This ignores
126
+ self.silent.
127
+ """
128
+ keys = self._tagdict.keys()
129
+ for tag in tags:
130
+ if tag not in keys:
131
+ raise KeyError("Tag '%s' does not exist" % tag)
132
+ return True
133
+
134
+ def __getObjectSet(self, tag):
135
+ """Helper function for getting an object set with given tag.
136
+
137
+ Raises KeyError if a passed tag does not exist and self.silent
138
+ is False
139
+ """
140
+ oset = self._tagdict.get(str(tag))
141
+ if oset is None:
142
+ if not self.silent:
143
+ raise KeyError("Tag '%s' does not exist" % tag)
144
+ oset = set()
145
+ return oset
146
+
147
+
148
+ # End class TagManager
149
+
150
+ # End of file
@@ -0,0 +1,170 @@
1
+ #!/usr/bin/env python
2
+ ##############################################################################
3
+ #
4
+ # diffpy.srfit Complex Modeling Initiative
5
+ # (c) 2016 Brookhaven Science Associates,
6
+ # Brookhaven National Laboratory.
7
+ # All rights reserved.
8
+ #
9
+ # File coded by: Pavol Juhas
10
+ #
11
+ # See AUTHORS.txt for a list of people who contributed.
12
+ # See LICENSE.txt for license information.
13
+ #
14
+ ##############################################################################
15
+ """Picklable storage of callable objects using weak references."""
16
+
17
+
18
+ import types
19
+ import weakref
20
+
21
+ import six
22
+
23
+
24
+ class WeakBoundMethod(object):
25
+ """Callable wrapper to a bound method stored as a weak reference.
26
+
27
+ Support storage of bound methods without keeping the associated objects
28
+ alive forever. Provide facility for a fallback function to be used
29
+ when method-related object is deleted.
30
+
31
+ Attributes
32
+ ----------
33
+ function : MethodType
34
+ the unbound function extracted from the wrapped bound method.
35
+ fallback : FunctionType, optional
36
+ plain function to be called when object holding the bound method
37
+ gets deallocated. The fallback function is called with this
38
+ object as the first argument followed by any positional and
39
+ keyword arguments passed for the bound method. The fallback
40
+ function can be used to deregister this wrapper.
41
+ _wref : weakref
42
+ weak reference to the object the wrapped method is bound to.
43
+ _class : type
44
+ the type of the object to which the method is bound.
45
+ This is only used for pickling.
46
+ """
47
+
48
+ __slots__ = ("function", "fallback", "_wref", "_class")
49
+
50
+ def __init__(self, f, fallback=None):
51
+ """Create a weak reference wrapper to bound method.
52
+
53
+ Parameters
54
+ ----------
55
+ f : bound MethodType
56
+ instance-bound method to be wrapped.
57
+ fallback : FunctionType, optional
58
+ plain function to be called instead of the bound method when
59
+ the method associated object gets deallocated.
60
+ """
61
+ # This does not handle builtin methods, but that can be added
62
+ # if necessary.
63
+ self.function = f.__func__
64
+ if six.PY2:
65
+ self.function = f.__func__.__get__(None, f.im_class)
66
+ self.fallback = fallback
67
+ self._class = type(f.__self__)
68
+ self._wref = weakref.ref(f.__self__)
69
+ return
70
+
71
+ def __call__(self, *args, **kwargs):
72
+ """Call the wrapped method if the weak-referenced object is alive.
73
+
74
+ If that object does not exist and the fallback function is defined,
75
+ call the fallback function instead.
76
+
77
+ Parameters
78
+ ----------
79
+ *args, **kwargs
80
+ same arguments as for the wrapped bound method.
81
+
82
+ Raises
83
+ ------
84
+ ReferenceError
85
+ when the method-bound object does not exist and the fallback
86
+ function is not defined.
87
+ """
88
+ mobj = self._wref()
89
+ if mobj is not None:
90
+ return self.function(mobj, *args, **kwargs)
91
+ if self.fallback is not None:
92
+ return self.fallback(self, *args, **kwargs)
93
+ emsg = "Object bound to {} does not exist.".format(self.function)
94
+ raise ReferenceError(emsg)
95
+
96
+ # support use of this class in hashed collections
97
+
98
+ def __hash__(self):
99
+ return hash((self.function, self._wref))
100
+
101
+ def __eq__(self, other):
102
+ rv = self.function == other.function and (
103
+ self._wref == other._wref or None is self._wref() is other._wref()
104
+ )
105
+ return rv
106
+
107
+ def __ne__(self, other):
108
+ return not self.__eq__(other)
109
+
110
+ # support pickling of this type
111
+
112
+ def __getstate__(self):
113
+ """Return state with a resolved weak reference."""
114
+ mobj = self._wref()
115
+ nm = self.function.__name__
116
+ amsg = "Unable to pickle this unbound function by name."
117
+ if six.PY2:
118
+ assert self.function == getattr(self._class, nm), amsg
119
+ if six.PY3:
120
+ assert self.function is getattr(self._class, nm), amsg
121
+ state = (self._class, nm, self.fallback, mobj)
122
+ return state
123
+
124
+ def __setstate__(self, state):
125
+ """Restore the weak reference in this wrapper upon unpickling."""
126
+ (self._class, nm, self.fallback, mobj) = state
127
+ self.function = getattr(self._class, nm)
128
+ if mobj is None:
129
+ # use a fake weak reference that mimics deallocated object.
130
+ self._wref = self.__mimic_empty_ref
131
+ return
132
+ # Here the referred object exists.
133
+ self._wref = weakref.ref(mobj)
134
+ return
135
+
136
+ @staticmethod
137
+ def __mimic_empty_ref():
138
+ return None
139
+
140
+
141
+ # end of class WeakBoundMethod
142
+
143
+
144
+ def weak_ref(f, fallback=None):
145
+ """Create weak-reference wrapper to a bound method.
146
+
147
+ Parameters
148
+ ----------
149
+ f : callable
150
+ object-bound method or a plain function.
151
+ fallback : FunctionType, optional
152
+ plain function to be called when object holding ``f`` gets
153
+ deallocated. The fallback function is called with the
154
+ wrapper object as the first argument followed by positional
155
+ and keyword arguments passed for the bound method. The
156
+ fallback function can be used to deregister the wrapper.
157
+
158
+ Returns
159
+ -------
160
+ WeakBoundMethod
161
+ when `f` is a bound method. If `f` is a plain function or
162
+ already of WeakBoundMethod type, return `f` and ignore the
163
+ `fallback` argument.
164
+ """
165
+ # NOTE Weak referencing plain functions is probably not needed,
166
+ # because they are already bound to the defining modules.
167
+ rv = f
168
+ if isinstance(f, (types.MethodType, types.BuiltinMethodType)):
169
+ rv = WeakBoundMethod(f, fallback=fallback)
170
+ return rv