| 42 | | typesNonBindMethods = (types.FunctionType, types.LambdaType, types.GeneratorType, types.BuiltinFunctionType) |
|---|
| 43 | | typesNonBind = typesNonBindMethods + (types.ClassType, types.ModuleType) |
|---|
| 44 | | |
|---|
| 45 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 46 | | #~ Callable Aspects |
|---|
| 47 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 48 | | |
|---|
| 49 | | #~ Test Utilities ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 50 | | |
|---|
| 51 | | def _PrintOnRelease(wr): |
|---|
| 52 | | print "Released weakref" |
|---|
| 53 | | |
|---|
| 54 | | class _TestObject(object): |
|---|
| 55 | | def TestMethod(self, *args, **kw): |
|---|
| 56 | | return args, kw |
|---|
| 57 | | |
|---|
| 58 | | #~ Class Bases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 59 | | |
|---|
| 60 | | class BoundCallableBase(object): |
|---|
| 61 | | """Ultimate baseclass of BoundCallable objects. Derive from this object if the class |
|---|
| 62 | | handles references to itself, by itself.""" |
|---|
| 63 | | pass |
|---|
| 64 | | |
|---|
| 65 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 66 | | |
|---|
| 67 | | class WeakBoundCallable(BoundCallableBase): |
|---|
| 68 | | """weakref for callable objects. |
|---|
| 69 | | |
|---|
| 70 | | Weakref objects have become a matter of necessity for managing the scope of interconnected |
|---|
| 71 | | networks of objects, defining owner and owned, while still allow for reference loops. |
|---|
| 72 | | However, weakref-ing a bound method always returns a dead weakref, but true references keep |
|---|
| 73 | | the "owner" class around. WeakBoundCallable's intention is to provide weakref-type support |
|---|
| 74 | | for bound methods, as well as all other callable objects. |
|---|
| 75 | | |
|---|
| 76 | | As a related sidenote, the ContextApply module builds upon this concepts to bind method variables |
|---|
| 77 | | to bound-callable objects. |
|---|
| 78 | | |
|---|
| 79 | | >>> t = _TestObject() |
|---|
| 80 | | >>> wbc = WeakBoundCallable(t.TestMethod, _PrintOnRelease) |
|---|
| 81 | | >>> wbc(1,2,3,four=4) |
|---|
| 82 | | ((1, 2, 3), {'four': 4}) |
|---|
| 83 | | >>> del t |
|---|
| 84 | | Released weakref |
|---|
| 85 | | |
|---|
| 86 | | """ |
|---|
| 87 | | |
|---|
| 88 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 89 | | #~ Constants / Variables / Etc. |
|---|
| 90 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 91 | | |
|---|
| 92 | | im_self = None |
|---|
| 93 | | im_func = None |
|---|
| 94 | | |
|---|
| 95 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 96 | | #~ Special |
|---|
| 97 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 98 | | |
|---|
| 99 | | def __init__(self, Callable, *weakrefArgs, **weakrefKw): |
|---|
| 100 | | if Callable is None: |
|---|
| 101 | | # A "None" method... it should expire immediately |
|---|
| 102 | | pass |
|---|
| 103 | | elif isinstance(Callable, BoundCallableBase): |
|---|
| 104 | | # One of those weird instances where we are proxying for an object very similar to ourself... |
|---|
| 105 | | # keep a hard reference to the BoundCallableBase |
|---|
| 106 | | self.im_func = Callable |
|---|
| 107 | | elif isinstance(Callable, typesBindMethods): |
|---|
| 108 | | # Wrap up the method and potential instance |
|---|
| 109 | | self.im_func = getattr(Callable, 'im_func', Callable) |
|---|
| 110 | | if getattr(Callable, 'im_self', None) is not None: |
|---|
| 111 | | self.im_self = weakref.ref(Callable.im_self, *weakrefArgs, **weakrefKw) |
|---|
| 112 | | else: |
|---|
| 113 | | pass |
|---|
| 114 | | elif isinstance(Callable, typesInstances): |
|---|
| 115 | | # This is a "callable object", make a weakref to it |
|---|
| 116 | | self.im_self = weakref.ref(Callable, *weakrefArgs, **weakrefKw) |
|---|
| 117 | | self.im_func = None |
|---|
| 118 | | elif callable(Callable): |
|---|
| 119 | | # What the heck is it? well... its supposed to be callable... |
|---|
| 120 | | self.im_func = Callable |
|---|
| 121 | | |
|---|
| 122 | | def __nonzero__(self): |
|---|
| 123 | | if self.im_self is not None: |
|---|
| 124 | | return self.im_self() is not None and 1 or 0 |
|---|
| 125 | | else: |
|---|
| 126 | | return self.im_func and 1 or 0 |
|---|
| 127 | | |
|---|
| 128 | | def __hash__(self): |
|---|
| 129 | | if self.im_self is not None: |
|---|
| 130 | | return hash(self.im_self) |
|---|
| 131 | | else: |
|---|
| 132 | | return hash(self.im_func) |
|---|
| 133 | | |
|---|
| 134 | | def __eq__(self, other): |
|---|
| 135 | | if isinstance(other, WeakBoundCallable): |
|---|
| 136 | | return (self.im_self == other.im_self) and (self.im_func == other.im_func) |
|---|
| 137 | | elif isinstance(other, weakref.ReferenceType): |
|---|
| 138 | | return self.im_func == other or self.im_self == other |
|---|
| 139 | | elif callable(other): |
|---|
| 140 | | return self == WeakBoundCallable(other) |
|---|
| 141 | | else: return self is other |
|---|
| 142 | | |
|---|
| 143 | | def __ne__(self, other): |
|---|
| 144 | | return not self.__eq__(other) |
|---|
| 145 | | |
|---|
| 146 | | def _DoCall(self, *args, **kw): |
|---|
| 147 | | if self.im_self: |
|---|
| 148 | | im_self = self.im_self() |
|---|
| 149 | | if im_self is not None: |
|---|
| 150 | | if self.im_func: |
|---|
| 151 | | return self.im_func(im_self, *args, **kw) |
|---|
| 152 | | else: return im_self(*args, **kw) |
|---|
| 153 | | else: raise weakref.ReferenceError, "weakly-referenced object no longer exists" |
|---|
| 154 | | else: return self.im_func(*args, **kw) |
|---|
| 155 | | |
|---|
| 156 | | #def __call__(self, *args, **kw): |
|---|
| 157 | | # return self._DoCall(args, kw) |
|---|
| 158 | | __call__ = _DoCall |
|---|
| 159 | | |
|---|
| 160 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 161 | | |
|---|
| 162 | | BoundCallable = WeakBoundCallable |
|---|
| 163 | | |
|---|
| 164 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 165 | | |
|---|
| 166 | | class StrongBoundCallable(BoundCallableBase): |
|---|
| 167 | | """Provides the same interface as WeakBoundCallable, but maintains strong references |
|---|
| 168 | | to both class instances and callable objects. Used mainly in ContextApply to build |
|---|
| 169 | | upon a common class interface. |
|---|
| 170 | | |
|---|
| 171 | | >>> t = _TestObject() |
|---|
| 172 | | >>> sbc = StrongBoundCallable(t.TestMethod) |
|---|
| 173 | | >>> sbc(1,2,3,four=4) |
|---|
| 174 | | ((1, 2, 3), {'four': 4}) |
|---|
| 175 | | """ |
|---|
| 176 | | |
|---|
| 177 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 178 | | #~ Constants / Variables / Etc. |
|---|
| 179 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 180 | | |
|---|
| 181 | | Callable = None |
|---|
| 182 | | |
|---|
| 183 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 184 | | #~ Special |
|---|
| 185 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 186 | | |
|---|
| 187 | | def __init__(self, Callable): |
|---|
| 188 | | if Callable is None: |
|---|
| 189 | | pass # A "None" method... it should expire immediately |
|---|
| 190 | | elif callable(Callable): |
|---|
| 191 | | self.Callable = Callable |
|---|
| 192 | | |
|---|
| 193 | | def __nonzero__(self): |
|---|
| 194 | | return self.Callable is not None and 1 or 0 |
|---|
| 195 | | |
|---|
| 196 | | def __hash__(self): |
|---|
| 197 | | return hash(self.Callable) |
|---|
| 198 | | |
|---|
| 199 | | def __eq__(self, other): |
|---|
| 200 | | if isinstance(other, StrongBoundCallable): |
|---|
| 201 | | return self.Callable == other.Callable |
|---|
| 202 | | elif callable(other): |
|---|
| 203 | | return self == StrongBoundCallable(other) |
|---|
| 204 | | else: return self is other |
|---|
| 205 | | |
|---|
| 206 | | def __ne__(self, other): |
|---|
| 207 | | return not self.__eq__(other) |
|---|
| 208 | | |
|---|
| 209 | | def _DoCall(self, *args, **kw): |
|---|
| 210 | | return self.Callable(*args, **kw) |
|---|
| 211 | | |
|---|
| 212 | | #def __call__(self, *args, **kw): |
|---|
| 213 | | # return self._DoCall(args, kw) |
|---|
| 214 | | __call__ = _DoCall |
|---|
| 215 | | |
|---|
| 216 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 217 | | |
|---|
| 218 | | def BindCallable(Callable, BindClass=BoundCallable, *args, **kw): |
|---|
| 219 | | """Binds a callable object using BindClass only if needed. |
|---|
| 220 | | |
|---|
| 221 | | >>> BindCallable(_PrintOnRelease) is _PrintOnRelease |
|---|
| 222 | | 1 |
|---|
| 223 | | >>> BindCallable(_TestObject.TestMethod) is not _TestObject.TestMethod |
|---|
| 224 | | 1 |
|---|
| 225 | | """ |
|---|
| 226 | | if isinstance(Callable, BoundCallableBase): |
|---|
| 227 | | # It's already bound in some form or another, |
|---|
| 228 | | # but we have to guard it from being wrapped |
|---|
| 229 | | # again, because it is itself an instance. |
|---|
| 230 | | return Callable |
|---|
| 231 | | elif isinstance(Callable, typesNonBind): |
|---|
| 232 | | # Doesn't need to be bound |
|---|
| 233 | | return Callable |
|---|
| 234 | | elif isinstance(Callable, typesRequireBinding): |
|---|
| 235 | | # Well if it requires binding, then we should do so! |
|---|
| 236 | | return BindClass(Callable, *args, **kw) |
|---|
| 237 | | else: |
|---|
| 238 | | # not quite sure what it is, but it does not require binding |
|---|
| 239 | | return Callable |
|---|
| 240 | | |
|---|
| 241 | | def WeakBindCallable(Callable, *args, **kw): |
|---|
| 242 | | """Weakly binds a callable object only if needed. |
|---|
| 243 | | |
|---|
| 244 | | >>> WeakBindCallable(_TestObject.TestMethod) is not _TestObject.TestMethod |
|---|
| 245 | | 1 |
|---|
| 246 | | >>> WeakBindCallable(_TestObject().TestMethod) and 1 or 0 |
|---|
| 247 | | 0 |
|---|
| 248 | | >>> t = _TestObject() |
|---|
| 249 | | >>> wbc = WeakBindCallable(t.TestMethod) |
|---|
| 250 | | >>> wbc is not t.TestMethod |
|---|
| 251 | | 1 |
|---|
| 252 | | >>> t.TestMethod() == wbc() |
|---|
| 253 | | 1 |
|---|
| 254 | | >>> del t |
|---|
| 255 | | >>> wbc and 1 or 0 |
|---|
| 256 | | 0 |
|---|
| 257 | | """ |
|---|
| 258 | | return BindCallable(Callable, WeakBoundCallable, *args, **kw) |
|---|
| 259 | | |
|---|
| 260 | | def StrongBindCallable(Callable, *args, **kw): |
|---|
| 261 | | """Strongly binds a callable object only if needed. |
|---|
| 262 | | |
|---|
| 263 | | >>> StrongBindCallable(_TestObject.TestMethod) is not _TestObject.TestMethod |
|---|
| 264 | | 1 |
|---|
| 265 | | >>> StrongBindCallable(_TestObject().TestMethod) and 1 or 0 |
|---|
| 266 | | 1 |
|---|
| 267 | | >>> t = _TestObject() |
|---|
| 268 | | >>> sbc = StrongBindCallable(t.TestMethod) |
|---|
| 269 | | >>> sbc is not t.TestMethod |
|---|
| 270 | | 1 |
|---|
| 271 | | >>> t.TestMethod() == sbc() |
|---|
| 272 | | 1 |
|---|
| 273 | | >>> del t |
|---|
| 274 | | >>> sbc and 1 or 0 |
|---|
| 275 | | 1 |
|---|
| 276 | | """ |
|---|
| 277 | | return BindCallable(Callable, StrongBoundCallable, *args, **kw) |
|---|
| 278 | | |
|---|
| 279 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 280 | | #~ Testing |
|---|
| 281 | | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|---|
| 282 | | |
|---|
| 283 | | if __name__ == '__main__': |
|---|
| 284 | | print "Testing..." |
|---|
| 285 | | import doctest, WeakBind |
|---|
| 286 | | doctest.testmod(WeakBind) |
|---|
| 287 | | |
|---|
| 288 | | import sys |
|---|
| 289 | | |
|---|
| 290 | | class _Test: |
|---|
| 291 | | def you(self): |
|---|
| 292 | | return self |
|---|
| 293 | | def me(self): |
|---|
| 294 | | return self |
|---|
| 295 | | |
|---|
| 296 | | def onrelease(wr): |
|---|
| 297 | | print "Released wr:", wr |
|---|
| 298 | | |
|---|
| 299 | | a = _Test() |
|---|
| 300 | | assert(sys.getrefcount(a) == 2) |
|---|
| 301 | | |
|---|
| 302 | | b1 = WeakBindCallable(a.me, onrelease) |
|---|
| 303 | | assert(b1) |
|---|
| 304 | | assert(sys.getrefcount(a) == 2) |
|---|
| 305 | | |
|---|
| 306 | | b2 = WeakBindCallable(a.me, onrelease) |
|---|
| 307 | | assert(b2) |
|---|
| 308 | | assert(sys.getrefcount(a) == 2) |
|---|
| 309 | | |
|---|
| 310 | | assert(b1 == b2) |
|---|
| 311 | | assert(sys.getrefcount(a) == 2) |
|---|
| 312 | | |
|---|
| 313 | | assert(a.me == b1) |
|---|
| 314 | | assert(a.me == b2) |
|---|
| 315 | | assert(b1 == b2) |
|---|
| 316 | | assert(b2 == b1) |
|---|
| 317 | | |
|---|
| 318 | | b3 = WeakBindCallable(a.you, onrelease) |
|---|
| 319 | | assert(not b1 == b3) |
|---|
| 320 | | assert(b1 != b3) |
|---|
| 321 | | |
|---|
| 322 | | del a |
|---|
| 323 | | |
|---|
| 324 | | try: |
|---|
| 325 | | b1() |
|---|
| 326 | | assert(0, "Shouldn't ever get here") |
|---|
| 327 | | except weakref.ReferenceError: |
|---|
| 328 | | pass |
|---|
| 329 | | |
|---|
| 330 | | try: |
|---|
| 331 | | if b2: |
|---|
| 332 | | b2() |
|---|
| 333 | | assert(0, "Shouldn't ever get here") |
|---|
| 334 | | except weakref.ReferenceError: |
|---|
| 335 | | assert(0, "Shouldn't ever get here") |
|---|
| 336 | | |
|---|
| 337 | | print "Test complete." |
|---|