11

I'm reaching back to my CLOS (Common Lisp Object System) days for this abstract question.

I'm augmenting the question to clarify:

It appears to me that a Python decorator is sort of like an "around" method in CLOS.

From what I remember, an "around" method in CLOS is a method/function that wraps around the primary method/function of the same name. It traverses up and down sub-classes too. Here's some syntax (I just grabbed my book).

All of these methods This would be inside a class:

(defmethod helloworld ()
  (format t "Hello World"))

There can be before and after methods too (which I'm throwing in for completeness):

(defmethod helloworld :before ()
  (format t "I'm executing before the primary-method"))

(defmethod helloworld :after ()
  (format t "I'm executing after the primary-method"))

And finally the around method (Notice here that this method seemed to be like a decorator):

(defmethod helloworld :around ()
  (format t "I'm the most specific around method calling next method.")
  (call-next-method)
  (format t "I'm the most specific around method done calling next method."))

I believe the output would be:

I'm the most specific around method calling next method. 
I'm executing before the primary-method
Hello World
I'm executing after the primary-method
I'm the most specific around method done calling next method.

I have always used this understanding of classes and their methods as a reference point for developing code. And unfortunately few languages seem to get this far with their method parameterization and power.

I'm pretty new to Python and am trying to see how decorators fit in. They seem a little looser in that a decorator can be a completely external function which yet has the ability to manipulate information within the calling information and even modifying the instance and class variables of the object called, and further that it seems to preform the role of the around method as shown here. But I was hoping somebody could help explain the relationship between decorators and around methods. I thought somebody would really like the opportunity to do that.

What makes CLOS powerful to me is that you can have multiple inheritance with these methods. Thus a class can be made up of superclasses that contain distinct functionalities and attributes which handle themselves. Thus an around method on one of the superclasses might terminate flow (if "call-next-method" is not done), just as the way a decorator can apparently work. So is this the same as a decorator, or different? In an around method, you're passing in the same arguments, but to a decorator, you're passing in the "function" in a strict definition which gets augmented. But is the outcome the same?

Thanks much! Maybe somebody could show closes approximation to the above in Python. done calling next method.

So the issue is not about implementing the CLOS methods in Python, but showing how close Python gets to that system in a pythonic way. Or showing how Python is actually better than that.

This is more of the kind of example I was thinking of:

class shape with attributes position and method area
class renderable with attribute visible and methods render, and render :around
class triangle with superclass shape and renderable attributes p1,p2,p3 and method render and method area
class square ...

If an instance of triangle has visible=false, then the render :around will not call the triangle's primary method.

In other words the calling chain of the render method is (a) renderable :around, (b) triangle primary, (c) finish renderable :around. If triangle had an :after method, it would be called after primary, and then the around method would finish up.

I understand the difficulties of using inheritance versus considering newer design patterns but here I'm trying to bridge my CLOS knowledge. If there's a design pattern that matches decorators (more accurately than the "decorator" design pattern), that would be great to understand also.

Conclusions

I'm getting the hang of decorators. But I wanted to present where I'm at with trying to emulate the CLOS method traversal. Everybody inspired me to try it since I've got the book and I remember it pretty well. Thanks all for all the great suggestions, they're all a piece of the puzzle. In terms of implementing the actual structure in a single decorator, Will got close and that's what worked for moving it forward with dynamic method finding (see below). I've created a single decorator that does what I'm looking for and can operate on any class. I'm sure it could be cleaner and there's a problem that it only looks up one superclass and it's doing around methods weirdly, but it does work.

'''file: cw.py'''
'''This decorator does the job of implementing a CLOS method traversal through superclasses.  It is a very remedial example but it helped me understand the power of decorators.'''
'''Modified based on Richards comments'''
def closwrapper(func): # *args, **kwargs  ?
    def wrapper(self):  #what about superclass traversals???
        name = func.__name__
        # look for the methods of the class 
        before_func = getattr(self, name + "_before", None)
        after_func = getattr(self, name + "_after", None)
        around_func = getattr(self, name + "_around", None)
        sup = super(self.__class__,self)
        #self.__class__.__mro__[1]
        if sup:
            # look for the supermethods of the class (should be recursive)
            super_before_func = getattr(sup,name + "_before", None)
            super_after_func = getattr(sup,name + "_after", None))
            super_around_func = getattr(sup,name + "_around", None))

        ''' This is the wrapper function which upgrades the primary method with any other methods that were found above'''
        ''' The decorator looks up to the superclass for the functions.  Unfortunately, even if the superclass is decorated, it doesn't continue chaining up.  So that's a severe limitation of this implementation.'''
        def newfunc():
            gocontinue = True
            supercontinue = True
            if around_func: 
                gocontinue = around_func() 
                if gocontinue and super_around_func:
                  supercontinue = super_around_func()
            if gocontinue and supercontinue:
                if before_func: before_func()
                if super_before_func: super_before_func()
                result = func(self)
                if super_after_func: super_after_func()   
                if after_func: after_func()              
            else:
                result = None
            if gocontinue:
                if super_around_func: super_around_func(direction="out")
            if around_func: around_func(direction='out')
            return result
        return newfunc()

    return wrapper

# Really, the way to do this is to have the decorator end up decorating
# all the methods, the primary and the before and afters.  Now THAT would be a decorator!

class weeclass(object):

    @closwrapper
    def helloworld(self):
        print "Hello Wee World"

    def helloworld_before(self):
        print "Am I really so wee Before?  This method is not called on subclass but should be"



class baseclass(weeclass):
    fooey = 1

    def __init__(self):
        self.calls = 0

    @closwrapper
    def helloworld(self):
        print "Hello World"

    def helloworld_before(self):
        self.fooey += 2
        print "Baseclass Before"

    def helloworld_after(self):
        self.fooey += 2
        print "Baseclass After Fooey Now",self.fooey

    def helloworld_around(self,direction='in'):
        if direction=='in': 
            print "Aound Start"
            if self.fooey < 10:
                return True
            else:
                print ">>FOOEY IS TOO BIG!!!"
                self.fooey = -10
                return False
        #call-next-method
        if not direction=='in': 
            #self.barrey -= 4  #hello??  This should not work!!!  It should croak?
            print "Around End"  



class subclass(baseclass): 
    barrey = 2

    @closwrapper
    def helloworld(self):
        print "Hello Sub World Fooey",self.fooey,"barrey",self.barrey

    def helloworld_before(self):
        self.fooey -= 1
        self.barrey += 5
        print "  Sub Before"

    def helloworld_after(self):
        print "Sub After"

    def helloworld_around(self,direction='in'):
        if direction=='in': 
            print "Sub Around Start"
            if self.barrey > 4:
                print ">>Hey Barrey too big!"
                self.barrey -= 8
                return False
            else:
                return True
        #call-next-method
        if not direction=='in': 
            self.barrey -= 4
            print "Sub Around End"  

Here is the output so you can see what I'm trying to do.

Python 2.6.4 (r264:75706, Mar  1 2010, 12:29:19)  
[GCC 4.2.1 (Apple Inc. build 5646) (dot 1)] on darwin  
Type "help", "copyright", "credits" or "license" for more information.  
>>> import cw  
>>> s= cw.subclass()  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 2 barrey 7  
Baseclass After Fooey Now 4  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 5 barrey 8  
Baseclass After Fooey Now 7  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey 8 barrey 9  
Baseclass After Fooey Now 10  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
>>Hey Barrey too big!  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
>>FOOEY IS TOO BIG!!!  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -9 barrey -6  
Baseclass After Fooey Now -7  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -6 barrey -5  
Baseclass After Fooey Now -4  
Sub After  
Around End  
Sub Around End  
>>> s.helloworld()  
Sub Around Start  
Aound Start  
Sub Before  
Baseclass Before  
Hello Sub World Fooey -3 barrey -4  
Baseclass After Fooey Now -1  
Sub After  
Around End  
Sub Around End  
>>> b = cw.baseclass()  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 5  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 9  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now 13  
Around End  
>>> b.helloworld()  
Aound Start  
>>FOOEY IS TOO BIG!!!  
Around End  
>>> b.helloworld()  
Aound Start  
Baseclass Before  
Am I really so wee Before?  This method is not called on subclass but should be  
Hello World  
Baseclass After Fooey Now -6  
Around End  

I hope that creates some understand of the CLOS calling and also sparks ideas on how to improve that decorator, or how to lambast me for even trying to do it. :-)

iJames
  • 640
  • 1
  • 7
  • 16
  • I'm happy that the others have been addressing your direct question, but this sentence sticks out at me: "I have always used this understanding of classes and their methods as a reference point for developing code." This strikes me as a very odd way to approach OO. If you are thinking of encapsulation, subclassing, etc. in terms of decorators and "before" and "after" code, I would ever-so-politely suggest you have another go at the concepts. Such unusual decorators should appear very rarely, if at all, in a typical OO architecture. – Oddthinking Sep 12 '10 at 12:45
  • Thanks Oddthinking. I realized, reviewing the answers below, (still working on understanding decorators, esp. in Django, and when to use them), that there's another element of the picture above that I hadn't included about CLOS. I'll edit the above to clarify what these methods are for. I think it'll shake up the answers below too. Basically, in CLOS all these methods defined above are part of a single class. So in this basic case, these methods could be represented by one method/function. It's only when subclassing that it gets interesting. – iJames Sep 13 '10 at 06:19
  • Ok, so I've built something of interest to the teeny Python/CLOS community. But it's based upon the conclusions of this thread. Where do I put it? – iJames Sep 14 '10 at 10:10
  • http://code.activestate.com/recipes/ is a standard place. I'm interested in it since decorators, metaclasses (which i assume you used), and inheritance are a pita. – Richard Levasseur Sep 15 '10 at 01:28
  • Oh, actually @Richard, I didn't know where to put it in the site here, but I guess I just extend my original question like I did above with the "Conclusion" section. (A little google wave playback would be handy). But I guess I could put it out there. It's definitely in a rudimentary state. Regarding Metaclasses, I didn't use them in Python. I don't think. I may be without knowing it? Got a link? – iJames Sep 15 '10 at 07:46
  • hehe...you'd know if you were using metaclasses :) (and no, you are not in the above code). A google search for "python metaclasses" is really the best starting point. Anyways, yeah, that decorator looks like what you want. There are a few code notes (you can use getattr(obj, name, *default*) without the try/except, and have the decorator accept args so you don't need to abide by the _before, _after, _around naming, the `newfunc` def could just be inlined, could use try/finally for the _after), but otherwise it looks like what you want. – Richard Levasseur Sep 15 '10 at 18:25
  • The only reason I even mentioned meta classes is that you'd be able to setup all the before/after/around stuff at "compile time", rather than having to look it up at run time. This is because the code used for making meta classes is executed when the class is defined (similar to how a decorator is execute when the function is defined). In later versions of python, there are class-level decorators, which might be easier than meta classes. In any event, good work :) – Richard Levasseur Sep 15 '10 at 18:27
  • @Richard thanks! I took a look at metaclasses. The concepts I'm trying to throw at Python here definitely sound like a metaclass issue now! Creating a nice "clostype" metaclass would give me the above decorator concept without the decorator right? Question: I'm still restricted from creating a custom syntax (Right?) which means the names of these "method partials" would still need to be there (see original defmethods at top for CLOS syntax). Also does Python 2 or 3 have anything like the paradigm I've described? Would you say that decorators and metaclasses can so it's really a DIY? – iJames Sep 16 '10 at 15:36
  • Its DIY, but its not exactly easy DIY. No, you cannot create custom syntax, you're still restricted to using Python syntax. Instead of _before, you can assign an attribute to the function (`func.before = before`) being wrapped (functions are objects like everything, so you can add attributes), or use a closure (as Will does in the accepted answer). – Richard Levasseur Sep 17 '10 at 07:03
  • Ok. So the trick there for adding attributes to functions is an wouldn't prevent overwriting of functions with the same name as used in CLOS with the args :before, :after, :around. So if I had def mymethod repeated in the code, the last def would clobber the first, yes? So above, creating different names for the functions is necessary huh? What I could do, though is have the metaclass look for those functions. One thing I don't like about mine is way the around is broken into multiple parts... I'll try to make other changes suggested above. J – iJames Sep 20 '10 at 23:05
  • @Richard. I'm not clear on how to inline that newfunc... – iJames Sep 20 '10 at 23:34
  • Not sure what you mean by a function of the same name being clobbered. If you give a function the same name as another within the same class, yeah, it'll be clobbered. If you mean, you name a function the same as in a parent class, it'll mask the parent (though it is still accessible using super()) – Richard Levasseur Sep 23 '10 at 01:44
  • Yep. Because in Lisp, they don't get clobbered: You can (defmethod helloworld () ()) and (defmethod helloworld :before () ()) etc. They're the same methods but the arguments and the protocol differentiates the two instead of clobbering them. So that's why in my code I used the "_before" concatenated to the function, so they wouldn't get clobbered. – iJames Sep 24 '10 at 07:43

4 Answers4

3

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 'Hello World!'

# 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
Hello World!
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 McCutchen
  • 13,047
  • 3
  • 44
  • 43
  • @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 08 '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 08 '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 08 '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 09 '10 at 00: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 09 '10 at 00:33
  • Your `around` method doesn't quite capture the essence of what around is capable of. See my example below. A callback can be passed to the decorating function that executes the function exactly where you want it to execute. As Donal Fellows said, you can implement `before` and `after` in terms of `around` –  Sep 12 '10 at 05:39
  • @Donal, right, it seems that a decorator too can handle the post-processing in addition to the pre-processing and can even skip "call-next-method" which, with decorators, is to not return any aspect of the original method? I would think exceptions are local to the individual functions, so the execution order is not impacted by an exception framework. Particular exceptions could alter the flow of around methods but could do nothing to before and after methods. From what I can tell, this type of function fragment (before and after) is not part of python. – iJames Sep 13 '10 at 08:32
  • Maybe this is apples and oranges here? A decorator is a completely separate class from the thing being decorated. While an around method is a polymorphic method of the same class or superclass or subclass. So are they just too different to compare? – iJames Sep 13 '10 at 08:38
  • Ok, I'm starting to get a deeper understanding here. Question about callable and instantiation. Callable question first. First, what if those methods being checked weren't callable yet? Since the wrapper seems to actually be a macro, the "callable" is called early. What if the methods pointed to don't exist? – iJames Sep 13 '10 at 11:57
  • Also, I'm trying to envision the above as a class. So instead of having these methods floating out there, they're within a class. And instead of having absolutely named methods, they're hinged off of the primary-method. So method helloworld() would be accompanied by def helloworld_before(), helloworld_after(), and helloworld_around(). Hmmm I think I'm close. I'm looking for a way to use getattr(instance,func.__name__ + "_before) or something like that to have the wrapper/decorator do everything... – iJames Sep 13 '10 at 11:57
2

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("Hello 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.

  • I'm reviewing the tutorial here: http://stackoverflow.com/questions/739654/understanding-python-decorators Nice! I'm getting the impression that I can have decorators within a class. Is that so? – iJames Sep 13 '10 at 09: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). –  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 07:42
2

Inspired by the original question and all that various drafts, I've implemented CLOS-like around/before/after auxiliary methods Python module.

See: http://code.activestate.com/recipes/577859-clos-like-aroundbeforeafter-auxiliary-methods/

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).
zuo
  • 21
  • 1
0

I'm not sure I understand :around, :before and :after very well, but is something like this what you are looking for?

class Base(object):
    def helloworld(self):
        print('Hello 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

  1. looks at self's MRO
  2. finds the next class after cls
  3. calls that class's helloworld method

So:

foo.helloworld()

yields

# Enter around
# Before
# Hello World
# After
# Exit around around

For more on super and MRO, see this excellent article by Shalabh Chaturvedi.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • 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 08:15