root/trunk/RBFoundation/RBFoundation/BindCallable.py

Revision 739, 9.9 kB (checked in by sholloway, 5 years ago)

*** empty log message ***

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23 #~ Imports
24 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
25
26 import weakref
27 import types
28
29 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
30 #~ Definitions
31 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
32
33 typesBindMethods = (types.MethodType, types.BuiltinMethodType )
34 typesInstances = (types.ObjectType, types.InstanceType)
35 typesRequireBinding = typesBindMethods + typesInstances
36
37 typesNonBindMethods = (types.FunctionType, types.LambdaType, types.GeneratorType, types.BuiltinFunctionType)
38 typesNonBind = typesNonBindMethods + (types.ClassType, types.ModuleType)
39
40 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41 #~ Callable Aspects
42 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43
44 class BoundCallableBase(object):
45     """Ultimate baseclass of BoundCallable objects.  Derive from this object if the class
46     handles references to itself, by itself."""
47     __slots__ = ()
48
49 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
50
51 class WeakBoundCallable(BoundCallableBase):
52     """weakref for callable objects.
53    
54     Weakref objects have become a matter of necessity for managing the scope of interconnected
55     networks of objects, defining owner and owned, while still allow for reference loops.
56     However, weakref-ing a bound method always returns a dead weakref, but true references keep
57     the "owner" class around.  WeakBoundCallable's intention is to provide weakref-type support
58     for bound methods, as well as all other callable objects.
59    
60     As a related sidenote, the ContextApply module builds upon this concepts to bind method variables
61     to bound-callable objects.
62    
63     """
64    
65     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66     #~ Special
67     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68
69     __slots__ = ('im_self', 'im_func')
70
71     def __init__(self, Callable, *weakrefArgs, **weakrefKw):
72         self.im_self = None
73         self.im_func = None
74         if Callable is None:
75             # A "None" method... it should expire immediately
76             pass
77         elif isinstance(Callable, BoundCallableBase):
78             # One of those weird instances where we are proxying for an object very similar to ourself...
79             # keep a hard reference to the BoundCallableBase
80             self.im_func = Callable
81         elif isinstance(Callable, typesBindMethods):
82             # Wrap up the method and potential instance
83             self.im_func = getattr(Callable, 'im_func', Callable)
84             if getattr(Callable, 'im_self', None) is not None:
85                 self.im_self = weakref.ref(Callable.im_self, *weakrefArgs, **weakrefKw)
86             else:
87                 pass
88         elif isinstance(Callable, typesInstances):
89             # This is a "callable object", make a weakref to it
90             self.im_self = weakref.ref(Callable, *weakrefArgs, **weakrefKw)
91             self.im_func = None
92         elif callable(Callable):
93             # What the heck is it?  well... its supposed to be callable...
94             self.im_func = Callable
95
96     def __nonzero__(self):
97         if self.im_self is not None:
98             return self.im_self() is not None and 1 or 0
99         else:
100             return self.im_func and 1 or 0
101
102     def __hash__(self):
103         if self.im_self is not None:
104             return hash(self.im_self)
105         else:
106             return hash(self.im_func)
107
108     def __eq__(self, other):
109         if isinstance(other, WeakBoundCallable):
110             return (self.im_self == other.im_self) and (self.im_func == other.im_func)
111         elif isinstance(other, weakref.ReferenceType):
112             return self.im_func == other or self.im_self == other
113         elif callable(other):
114             return self == WeakBoundCallable(other)
115         else: return self is other
116
117     def __ne__(self, other):
118         return not self.__eq__(other)
119
120     def _DoCall(self, *args, **kw):
121         if self.im_self:
122             im_self = self.im_self()
123             if im_self is not None:
124                 if self.im_func:
125                     return self.im_func(im_self, *args, **kw)
126                 else: return im_self(*args, **kw)
127             else: raise weakref.ReferenceError, "weakly-referenced object no longer exists"
128         else: return self.im_func(*args, **kw)
129
130     #def __call__(self, *args, **kw):
131     #    return self._DoCall(args, kw)
132     __call__ = _DoCall
133
134 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
135
136 BoundCallable = WeakBoundCallable
137
138 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
139
140 class StrongBoundCallable(BoundCallableBase):
141     """Provides the same interface as WeakBoundCallable, but maintains strong references
142     to both class instances and callable objects.  Used mainly in ContextApply to build
143     upon a common class interface.
144     """
145
146     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147     #~ Special
148     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
149
150     __slots__ = ('im_func',)
151
152     def __init__(self, Callable):
153         self.im_func = Callable
154
155     def __nonzero__(self):
156         return self.im_func is not None and 1 or 0
157
158     def __hash__(self):
159         return hash(self.im_func)
160
161     def __eq__(self, other):
162         if isinstance(other, StrongBoundCallable):
163             return self.im_func == other.im_func
164         elif callable(other):
165             return self == StrongBoundCallable(other)
166         else: return self is other
167
168     def __ne__(self, other):
169         return not self.__eq__(other)
170
171     def _DoCall(self, *args, **kw):
172         return self.im_func(*args, **kw)
173
174     #def __call__(self, *args, **kw):
175     #    return self._DoCall(args, kw)
176     __call__ = _DoCall
177
178 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179
180 class Curry(StrongBoundCallable):
181     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
182     #~ Special
183     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
184
185     __slots__ = ('args', 'kw')
186
187     def __init__(self, callback, *args, **kw):
188         StrongBoundCallable.__init__(self, callback)
189         self.args = args
190         self.kw = kw
191
192     def __call__(self):
193         return self._DoCall(*self.args, **self.kw)
194
195     #~ tuple form ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
196
197     def asTuple(self):
198         return (self.im_func, self.args, self.kw)
199
200     def fromTuple(klass, currytuple):
201         call, args, kw = currytuple + ((), {})[len(currytyple)-1:]
202         return klass(call, *args, **kw)
203     fromTuple = classmethod(fromTuple)
204
205     def callTuple(currytuple):
206         call, args, kw = currytuple + ((), {})[len(currytyple)-1:]
207         return call(*args, **kw)
208     callTuple = staticmethod(callTuple)
209
210 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
211
212 def BindCallableEx(Callable, BindClass=BoundCallable, *args, **kw):
213     """Binds a callable object using BindClass only if needed."""
214     if isinstance(Callable, BoundCallableBase):
215         # It's already bound in some form or another,
216         # but we have to guard it from being wrapped
217         # again, because it is itself an instance.
218         return Callable
219     elif isinstance(Callable, typesNonBind):
220         # Doesn't need to be bound
221         return Callable
222     elif isinstance(Callable, typesRequireBinding):
223         # Well if it requires binding, then we should do so!
224         return BindClass(Callable, *args, **kw)
225     else:
226         # not quite sure what it is, but it does not require binding
227         return Callable
228
229 def BindCallable(Callable, *args, **kw):
230     return BindCallableEx(Callable, BoundCallable, *args, **kw)
231 Bind = BindCallable
232
233 def WeakBindCallable(Callable, *args, **kw):
234     """Weakly binds a callable object only if needed."""
235     return BindCallableEx(Callable, WeakBoundCallable, *args, **kw)
236 WeakBind = WeakBindCallable
237
238 def StrongBindCallable(Callable, *args, **kw):
239     """Strongly binds a callable object only if needed."""
240     return BindCallableEx(Callable, StrongBoundCallable, *args, **kw)
241 StrongBind = StrongBindCallable
242
243 def CurryCallable(Callable, *args, **kw):
244     """Binds a curry object if needed"""
245     if args or kw:
246         return Curry(Callable, *args, **kw)
247     else:
248         return Callable
249
250 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
251 #~ Testing
252 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253
254 if __name__ == '__main__':
255     print "Testing..."
256     import sys
257
258     class _Test:
259         def you(self):
260             return self
261         def me(self):
262             return self
263    
264     def onrelease(wr):
265         print "Released wr:", wr
266
267     a = _Test()
268     assert(sys.getrefcount(a) == 2)
269
270     b1 = WeakBindCallable(a.me, onrelease)
271     assert(b1)
272     assert(sys.getrefcount(a) == 2)
273
274     b2 = WeakBindCallable(a.me, onrelease)
275     assert(b2)
276     assert(sys.getrefcount(a) == 2)
277
278     assert(b1 == b2)
279     assert(sys.getrefcount(a) == 2)
280
281     assert(a.me == b1)
282     assert(a.me == b2)
283     assert(b1 == b2)
284     assert(b2 == b1)
285
286     b3 = WeakBindCallable(a.you, onrelease)
287     assert(not b1 == b3)
288     assert(b1 != b3)
289
290     del a
291
292     try:
293         b1()
294         assert(0, "Shouldn't ever get here")
295     except weakref.ReferenceError:
296         pass
297
298     try:
299         if b2:
300             b2()
301             assert(0, "Shouldn't ever get here")
302     except weakref.ReferenceError:
303         assert(0, "Shouldn't ever get here")
304
305     print "Test complete."
Note: See TracBrowser for help on using the browser.