Demonstration of Decorators:

(Adapted from Jon Jacky's Intro to Python class)

Creating a function in a function....


In [24]:
def addn(n):
    def adder(i):
        return i + n
    return adder

NOTE: you oculd use lambda for something as simple as this...


In [25]:
add2 = addn(2)

In [26]:
add2 (1)


Out[26]:
3

In [27]:
add3 = addn(3)

In [28]:
add3(1)


Out[28]:
4

A function that takes a function as an argument, and returns a function can be a decorator.

It usually creates a function inside its scope...

Pass a function as an argument, use that to define the function you return.

(first a couple functions to use...)


In [29]:
def odd(i):
    return i%2
def even(i):
    return not odd(i)

And write a wrapper for them....


In [30]:
def sieve(f):
    def siever(s):
        return [x for x in s if f(x)]
    return siever

Make a couple of sieves:


In [31]:
oddsieve = sieve(odd)
evensieve = sieve(even)

And try them out:


In [32]:
s = range(10)
s


Out[32]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [33]:
oddsieve(s)


Out[33]:
[1, 3, 5, 7, 9]

In [34]:
evensieve(s)


Out[34]:
[0, 2, 4, 6, 8]

The decorator operator @ abbreviates the preceding pattern

@f def g means

g = f(g)


In [35]:
@sieve
def osieve(i):
    return i % 2

@sieve
def esieve(i):
    return not (i % 2)

In [36]:
osieve(s)


Out[36]:
[1, 3, 5, 7, 9]

In [37]:
esieve(s)


Out[37]:
[0, 2, 4, 6, 8]

A callable class can be used as a function, so you can also use a class as a decorator

(classes and objects are callable (via __init__ and __call__))


In [38]:
class Memoize:
    """
    memoize decorator from avinash.vora
    http://avinashv.net/2008/04/python-decorators-syntactic-sugar/
    """
    def __init__(self, function):  # runs when memoize class is called
        self.function = function
        self.memoized = {}

    def __call__(self, *args):  # runs when memoize instance is called
        try:
            return self.memoized[args]
        except KeyError:
            self.memoized[args] = self.function(*args)
            return self.memoized[args]

To use it -- the nifty decorator syntax:


In [39]:
@Memoize        # same effect as sum2x = memoize(sum2x)
def sum2x(n):
    return sum(2 * i for i in xrange(n))  # takes time when n > 10 million

call it:


In [40]:
sum2x(10)


Out[40]:
90

In [41]:
sum2x(10)


Out[41]:
90

But slow if you call it with a big number:


In [42]:
import time
start = time.clock()
sum2x(10000000)
print "it took %f seconds to run"%(time.clock() - start)


it took 1.580831 seconds to run

But the second time...


In [43]:
import time
start = time.clock()
sum2x(10000000)
print "it took %f seconds to run"%(time.clock() - start)


it took 0.000192 seconds to run

Quiz time: what type of object is sum2x ?


In [44]:
repr(sum2x)


Out[44]:
'<__main__.Memoize instance at 0x103708170>'

In [ ]: