Demonstration of Decorators:

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

Creating a function in a function....


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

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


In [2]:
add2 = addn(2)

In [3]:
add2 (1)


Out[3]:
3

In [4]:
add3 = addn(3)

In [5]:
add3(1)


Out[5]:
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 [6]:
def odd(i):
    return i%2
def even(i):
    return not odd(i)

And write a wrapper for them....


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

Make a couple of sieves:


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

And try them out:


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


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

In [11]:
oddsieve(s)


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

In [12]:
evensieve(s)


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

The decorator operator @ abbreviates the preceding pattern

@f def g means

g = f(g)


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

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

In [16]:
osieve(s)


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

In [17]:
esieve(s)


Out[17]:
[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 [18]:
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 [19]:
@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 [20]:
sum2x(10)


Out[20]:
90

In [21]:
sum2x(10)


Out[21]:
90

But slow if you call it with a big number:


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


it took 0.968653 seconds to run

But the second time...


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


it took 0.000184 seconds to run

Quiz time: what type of object is sum2x ?


In [24]:
repr(sum2x)


Out[24]:
'<__main__.Memoize instance at 0x102f80488>'

In [ ]: