Here's a quick and dirty implementation slightly better implementation (now with the around method called hopefully in the right places), using decorators def hints(before=None, after=None, around=None): """A decorator that implements function hints to be run before, after or around another function, sort of like in the CLOS. """ # Make sure all of our hints are callable default = lambda *args, **kwargs: None before = before if callable(before) else default after = after if callable(after) else default around = around if callable(around) else default # The actual decorator function. The "real" function to be called will be # pased to this as `fn` def decorator(fn): # The decorated function.
This is where the work is done. The before # and around functions are called, then the "real" function is called # and its results are stored, then the around and after functions are # called. Def decorated(*args, **kwargs): around(*args, **kwargs) before(*args, **kwargs) result = fn(*args, **kwargs) after(*args, **kwargs) around(*args, **kwargs) return result return decorated return decorator # Shortcuts for defining just one kind of hint def before(hint): return hints(before=hint) def after(hint): return hints(after=hint) def around(hint): return hints(around=hint) # The actual functions to run before, after, around def beforefn(): print 'before' def afterfn(): print 'after' def aroundfn(): print 'around' # The function around which the other functions should run @before(beforefn) @after(afterfn) @around(aroundfn) def fn(): print '' # Or use the single @hints decorator @hints(before=beforefn, after=afterfn, around=aroundfn) def fn2(): print 'Goodbye World!
Calling fn() results in this: fn() around before After around >>> fn2() around before Goodbye World! After around The decorators in this case might be a little bit confusing, because there are two nested functions involved in each, rather than the one nested function seen in a lot of decorators It might not be as elegant as the CLOS version (and I may be missing some of its functionality), but it seems to do what you want.
Here's a quick and dirty implementation slightly better implementation (now with the around method called hopefully in the right places), using decorators def hints(before=None, after=None, around=None): """A decorator that implements function hints to be run before, after or around another function, sort of like in the CLOS. """ # Make sure all of our hints are callable default = lambda *args, **kwargs: None before = before if callable(before) else default after = after if callable(after) else default around = around if callable(around) else default # The actual decorator function. The "real" function to be called will be # pased to this as `fn` def decorator(fn): # The decorated function.
This is where the work is done. The before # and around functions are called, then the "real" function is called # and its results are stored, then the around and after functions are # called. Def decorated(*args, **kwargs): around(*args, **kwargs) before(*args, **kwargs) result = fn(*args, **kwargs) after(*args, **kwargs) around(*args, **kwargs) return result return decorated return decorator # Shortcuts for defining just one kind of hint def before(hint): return hints(before=hint) def after(hint): return hints(after=hint) def around(hint): return hints(around=hint) # The actual functions to run before, after, around def beforefn(): print 'before' def afterfn(): print 'after' def aroundfn(): print 'around' # The function around which the other functions should run @before(beforefn) @after(afterfn) @around(aroundfn) def fn(): print '' # Or use the single @hints decorator @hints(before=beforefn, after=afterfn, around=aroundfn) def fn2(): print 'Goodbye World!' Calling fn() results in this: >>> fn() around before After around >>> fn2() around before Goodbye World!
After around The decorators in this case might be a little bit confusing, because there are two nested functions involved in each, rather than the one nested function seen in a lot of decorators. It might not be as elegant as the CLOS version (and I may be missing some of its functionality), but it seems to do what you want.
Will, thank for the example and revised example! I think this will definitely help me adjust my cognitive mapping of programming to include this construct! Now I'll parse it a couple of times and see if it'll sit down in my brain.
– iJames Sep 8 '10 at 23:52 In principle it's not far off, but you've not handled exceptions or the way that around is supposed to be able to also post-process results. Not impossible to work around, but a little messy. (You can implement before and after in terms of around, if that makes it easier…) – Donal Fellows Sep 8 '10 at 23:59 Note for CLOS perspective: In a class hierarchy, the most specific :around method is called first (meaning the instance object's :around method).
It then goes up to the superclass to call the next :around method. It then does the same for :before starting with the most-specific again. Then it calls the primary-method.
Then most specific :after methods. After this plays out, it would traverse back down into any around methods that had code after the call-next-method. (This differs from the above in that the "befores" are called before the around methods.
Not sure about sub-classes yet. ) – iJames Sep 8 '10 at 23:59 @Donal & @iJames, yeah, I was sure that this implementation would pale in comparison to the CLOS's implementation. I was just hoping that it would illustrate one approach to using decorators to accomplish something similar in principal (as you pointed out).
Out of curiosity, how would you expect exceptions to be handled by these decorators, and how would the around method be able to post-process the results? – Will McCutchen Sep 9 '10 at 0:30 And I update my example to call the around functions in the right place, I think. That was just a dumb oversight on my part.
– Will McCutchen Sep 9 '10 at 0:33.
You could implement something similar. Will was on the right track, but it seems like "call-next-method" is pretty pivotal to the use of "around", which can be implemented as such: def around(callback): def decorator(fn): return lambda *a, **kw: callback(lambda: fn(*a, **kw)) return decorator def hello_before(call_next_method): print("I'm executing before the primary-method") return call_next_method() def hello_after(call_next_method): value = call_next_method() print("I'm executing after the primary-method") return value def hello_around(call_next_method): print "I'm the most specific around method calling next method. " value = call_next_method() print("I'm the most specific around method done calling next method.") return value @around(hello_around) @around(hello_after) @around(hello_before) def helloworld(): print(" world") helloworld() This produces exactly the same output as yours, with reasonably similar constructs.
Just pay attention to the order you decorate the function with.
Ah, that's a nice idea! – Will McCutchen Sep 12 '10 at 18:02 I'm reviewing the tutorial here: stackoverflow. Com/questions/739654/… Nice!
I'm getting the impression that I can have decorators within a class. Is that so? – iJames Sep 13 '10 at 9:12 Not sure exactly what you're asking, but the answer is yes :) You can decorate methods, you can use methods as decorators, and you can decorate classes, as of 2.6 (where your class is passed to the decorating function to perform alterations of class definitions).
– wybiral Sep 13 '10 at 22:54 Yep, got it. See my above conclusion. I think it's sortof a "module" where it contains a decorator and a class and the decorator decorates the methods of the class.
I also provided sample output. Let me know what you think! Versus yours above, notice how I have one decorator that looks for all before, after, around methods if they exist for the primary method, including the superclass's methods.It's not perfect, but it's a first pass at the notion.
– iJames Sep 15 '10 at 7:42.
Inspired by the original question and all that various drafts, I've implemented CLOS-like around/before/after auxiliary methods Python module. See: code.activestate.com/recipes/577859-clos... I've done it using a few native Python features: class and function decorators, class inheritance plus the super() built-in function, private name mangling (to free users from necessity of redundant class name retyping).
I'm excited to take a look at this! – iJames Aug 27 at 17:31.
Class Base(object): def helloworld(self): print(' World') class After(object): def helloworld(self): super(After,self).helloworld() print('After') class Before(object): def helloworld(self): print('Before') super(Before,self).helloworld() class Around(object): def helloworld(self): print('Enter around') super(Around,self).helloworld() print('Exit around around') class Foo(Around,Before,After,Base): def helloworld(self): super(Foo,self).helloworld() foo=Foo() This is foo's MRO (method resolution order). Print(cls. __name__ for cls in foo.
__class__.mro()) # 'Foo', 'Around', 'Before', 'After', 'Base', 'object' When you say super(cls,self).helloworld(), Python looks at self's MRO finds the next class after cls calls that class's helloworld method So: foo.helloworld() yields # Enter around # Before # World # After # Exit around around For more on super and MRO, see this excellent article by Shalabh Chaturvedi.
Aside from the multiple classes, this does capture one aspect of the CLOS capability which is execution flow. But there are a couple of issues I see. First is that only the one most specific primary-method (Foo's helloworld) should be called.So I would think it's: Around, Before, Foo, After.
But the way you have represented the Around, compared to the Before and After gets us closer, and it demonstrates the ability to traverse up and down the inheritance tree. But could it be done all within one class? And how does this Around method compare to a pythonic python decorator?
Thanks! – iJames Sep 13 '10 at 8:15.
I'm the most specific around method calling next method. I'm the most specific around method done calling next method. '''This decorator does the job of implementing a CLOS method traversal through superclasses.
Def closwrapper(func): # *args, **kwargs? Def wrapper(self): #what about superclass traversals? Name = func.
I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.