Log in / create account | Login with OpenID
DocForge
An Open Wiki For Software Developers

Python function decorators

From DocForge

Python 2.4a2 introduced function decorators to the language.

Decorators are an elegant way to add features to functions and methods. Any callable object that accepts a function object as its first parameter can be a decorator.

For example, Python has two built-in functions that can be used as decorators, classmethod and staticmethod.

class SampleClass(object):
    @staticmethod
    def my_static_method():
        pass
    
    @classmethod
    def my_class_method(cls):
        pass

The simplest way to explain this is that a function decorator takes a function object and replaces it with a callable object of some kind. Combined with python's excellent support of closures, decorators are an extremely powerful addition to the language.

For example, here's a decorator that caches the result of the function it's applied to, and caches it for a specific number of seconds:

def cached(timeout):
    def _cached(func):
        func.expires = 0
        def __cached(*args, **kwargs):
            if(timeout and func.expires < time.time() and hasattr(func, 'cache')):
                del func.cache
            if(hasattr(func, 'cache')):
                return func.cache
            result = func(*args, **kwargs)
            if(result):
                func.cache = result
                func.expires = time.time() + timeout
            return result
        return __cached
    try:
        # see if it's an int
        int(timeout)
    except TypeError:
        func = timeout
        timeout = 0
        return _cached(func)
    return _cached
 
@cached(120)
def some_heavyweight_calculation():
    #database queries, prime factorization, and the like
    return result

This is useful when querying databases in long-running applications, such as threaded web apps. If you have a summary page or some infrequently-changing content that is created by a complex database query, you can cache the results for however long you wish.

Of course the problem with the code above is that it only works with plain functions, not instance methods. Since only one copy of the function is shared among all instances of a class, you can't use this decorator to cache the results of a an instance method.

Something like this can work for methods:

def cachedmethod(timeout):
    def _cached(func):
        if not(hasattr(func, 'expires')):
            func.expires = {}
            func.cache = {}
        def __cached(self, *args, **kwargs):
            if(timeout and func.expires.get(repr(self), 0) < time.time()):
                if(repr(self) in func.cache):
                    del func.cache[repr(self)]
            if(repr(self) in func.cache):
                return func.cache[repr(self)]
            result = func(self, *args, **kwargs)
            if(result):
                func.cache[repr(self)] = result
                func.expires[repr(self)] = time.time() + timeout
            return result
        return __cached
    try:
        # see if it's an int
        int(timeout)
    except TypeError:
        func = timeout
        timeout = 0
        return _cached(func)
    return _cached