środa, 12 października 2011

Python decorators tamed

In last week I was trying to write a decorator which will temporarily cache a method result in an object instance- the description probably isn't very useful but the decorator itself is.

What i wanted to achieve:

  1. Have a decorator...
  2. ...which can be used to methods in classes,
  3. ...which can be ised with and without arguments,
  4. ...and which has access to the actual instance('self' arg) of method
First of all, we need to know how Python decorators work:


def foo(function):
    print "I'm a decorator"
    return function

@foo
def bar(a):
    print a
    print "BAR"


@foo works like that:
bar = foo(bar)
And it can't be simpler - the foo function just must return a function(it can be bar or any other function including lambda expression)

def foo(x,y):
    print x,y
    print "I'm not a real decorator"
    def real_decorator(function):
        print "I'm a decorator"
        return function
    return real_decorator

@foo('baz','qwe')
def bar(x):
    print x
    print "BAR"




Here we have a little bit more complicated case:
@foo('baz','qwe') means:
bar = foo('baz','qwe')(bar)
So- the foo function must handle 'baz' and 'qwe' and return a function which can handle bar function

But what if you need to handle arguments passed to bar function?

def foo(function):
    def real_decorator(x,y,z):
        print "We are the decorated x=%s, y=%s, z=%s" % (x,y,z)
        function(x,y,z)
        return function
    return real_decorator

     
@foo
def bar(x,y,z):
    print "BAR"


Again- @foo works like that:
bar = foo(bar) - so foo must return a function which handle the same args as bar function

But- what happens if you want to use foo decorator with or without arguments? Answer is simple- you must check type of the outermost function argument.


What is the answer?

def foo(baz=None):
    def decorate(function):
        def decorate_inside(self, *args, **kwargs):
            real_value = self ** 2

            function()
            setattr(self, 'foobarbaz', real_value)
            return real_value
        return decorate_inside

    mytype = type(baz)
    if mytype == types.MethodType or mytype == types.FunctionType:
        return decorate(baz)
    else:

        print baz
        return decorate



@foo
def bar(x)
    print x


@foo('a')
def baz(x)
    print x

And this is what you want. Of course you can use this decorator on methods inside classess too- and self will be the object instance which can be modified.

0 komentarze:

Prześlij komentarz