diff --git a/util/aop.py b/util/aop.py index 65f80d4..a66a1f6 100644 --- a/util/aop.py +++ b/util/aop.py @@ -34,13 +34,22 @@ class Notifier(object): def __init__(self, method): self.method = method obj = method.im_self or method.im_class - name = method.im_func.func_name + name = self.name = method.im_func.func_name setattr(obj, name, self) self.beforeSubs = [] self.afterSubs = [] def __get__(self, instance, cls=None): - return BoundNotifier(self, instance) + if instance is None: + # class-level access + return self + existing = instance.__dict__.get(self.name) + if isinstance(existing, BoundNotifier): + # the instance's method is already wrapped + return existing + notifier = BoundNotifier(self, instance) + setattr(instance, self.name, notifier) + return notifier def __call__(self, *args, **kw): for sub, sargs, skw in self.beforeSubs: @@ -62,15 +71,21 @@ class BoundNotifier(object): def __init__(self, context, instance): self.context = context self.instance = instance + self.beforeSubs = [] + self.afterSubs = [] def __call__(self, *args, **kw): - for sub, sargs, skw in self.context.beforeSubs: + context = self.context + for sub, sargs, skw in self.beforeSubs + context.beforeSubs: sub(None, *sargs, **skw) - result = self.context.method(self.instance, *args, **kw) - for sub, aargs, akw in self.context.afterSubs: + result = context.method(self.instance, *args, **kw) + for sub, aargs, akw in self.afterSubs + context.afterSubs: sub(result, *aargs, **akw) return result def subscribe(self, before, after, *args, **kw): - self.context.subscribe(before, after, *args, **kw) + if before is not None: + self.beforeSubs.append((before, args, kw)) + if after is not None: + self.afterSubs.append((after, args, kw)) diff --git a/util/aop.txt b/util/aop.txt index b96d0a0..562faaa 100644 --- a/util/aop.txt +++ b/util/aop.txt @@ -28,8 +28,8 @@ Calling the wrapped method still works. >>> demo.foo(8) 46 -We can now get information about the returning of the method by -subscribing to it using some log function. +We can now get information about the start and end of the method call by +subscribing to it using a logging routine. >>> def log(result, msg): ... print 'logging: %s, result=%s' % (msg, str(result)) @@ -51,14 +51,36 @@ subscribing to it using some log function. Wrapping methods on class level =============================== +The above code did only wrap the bound method ``foo()`` of a certain +instance of the ``Demo`` class. Other instances of this class are not +affected. + >>> demo = Demo() >>> demo.foo(4) 42 +But we may also wrap the class itself and subscribe to the method on +the class level. + >>> getNotifier(Demo.foo) <...Notifier object ...> >>> Demo.foo.subscribe(None, log, 'after foo') + +This now affects all instances of the class. + >>> demo.foo(4) logging: after foo, result=42 42 + >>> demo.foo(8) + logging: after foo, result=46 + 46 + +Combining class and instance level wrapping +------------------------------------------- + + >>> demo.foo.subscribe(None, log, 'after demo.foo') + >>> demo.foo(10) + logging: after demo.foo, result=48 + logging: after foo, result=48 + 48