import weakref import itertools import types def updated_kw_copy(dict1, dict2): d = dict1.copy() d.update(dict2) return d class CallbackError(Exception): """Called for errors in Callback classes""" class DeadCallback(CallbackError): """Called when the callback contains a dead reference""" class Callback(object): def __init__(self, func, args=(), kw={}, weak=True): self.weak = weak if weak: self.func = weakref.ref(func) else: self.func = func self.args = args self.kw = kw def __nonzero__(self): if not self.weak: return True if not self.func() is None: return True return False def __call__(self, *args, **kw): """Calls the refernced function with local and global args/kw The function saved as self.func is called. The arguments given to the call-method are extended by the arguments that are saved in the Callback instance, which means that the call-arguments come before the local arguments. The keyword-args given to the call-method update a copy of the local keyword-args initially given, which means that the latter are overridden by the keyword-args of the call-method. """ args = args + self.args kw = updated_kw_copy(self.kw, kw) if self.weak: if not self.func(): raise DeadCallback, "The referenced function is dead!" return return self.func()(*args, **kw) else: return self.func(*args, **kw) class CallbackManager(object): COUNTER = itertools.count() def __init__(self, pre=None, post=None, internal=None): self._pre = None self._internal = None self._post = None def bind_pre(self, func, args=(), kw=[], insertpos="end", weak=False): if not self._pre: self._pre = [] self._bind(func, target="pre", args=args, kw=kw, insertpos=insertpos, weak=weak) def bind_internal(self, func, args=(), kw={}, insertpos="end", weak=False): if not self._internal: self._internal = [] self._bind(func, target="internal", args=args, kw=kw, insertpos=insertpos, weak=weak) def bind_post(self, func, args=(), kw={}, insertpos="end", weak=False): if not self._post: self._post = [] self._bind(func, target="post", args=args, kw=kw, insertpos=insertpos, weak=weak) def _bind(self, func, target, args, kw, insertpos, weak): try: callbacks = getattr(self, "_%s"%target) except AttributeError: raise AttributeError, "target '%s' is not supported" % target return cb = Callback(func, args, kw, weak) if insertpos == "end": callbacks.append(cb) elif type(insertpos) in (types.IntType, types.LongType): callbacks.insert(insertpos, cb) def bind(self, func, targets=[], args=(), kw={}, insertpos="end", weak=False, *_args, **_kw): args = _args + args kw.update(_kw) for target in ("pre", "internal", "post"): if target in targets: bind_func = getattr(self, "bind_" + target) bind_func(func, args=args, kw=kw, insertpos=insertpos, weak=weak) def do_pre(self, *gen_args, **gen_kw): if not self._pre: return return self._do("pre", *gen_args, **gen_kw) def do_post(self, *gen_args, **gen_kw): if not self._post: return return self._do("post", *gen_args, **gen_kw) def do_internal(self, *gen_args, **gen_kw): if not self._internal: return return self._do("internal", *gen_args, **gen_kw) def _do(self, target, *gen_args, **gen_kw): try: callbacks = getattr(self, "_%s"%target) except AttributeError: raise AttributeError, "target '%s' is not supported" % target return for cb in callbacks: try: err = cb(*gen_args, **gen_kw) if err: return err except DeadCallback, s: print "Toter Callback:", s callbacks.remove(cb) if not callbacks: setattr(self, "_%s"%target, None) def do(self, *gen_args, **gen_kw): return (self.do_pre(*gen_args, **gen_kw) or self.do_internal(*gen_args, **gen_kw) or self.do_post(*gen_args, **gen_kw)) def out(*args, **kw): print "Args:", args print "Keywords:", kw print "=" * 40 if __name__ == "__main__": cbm = CallbackManager() #cb.bind(out, ["pre", "post"], "hallo", text="welt") args = ("hallo",) kw = {"text":"welt"} cbm.bind_pre(out, args=("pre",), kw=kw, weak=True) cbm.bind_internal(out, args=args, kw=kw, weak=True) print cbm._pre, cbm._post, cbm._internal del out cbm.do("Michael", tst="test") print cbm._pre, cbm._post, cbm._internal