# DecoratorDemo

## Demonstration of Decorators:

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

``````

### Creating a function in a function....

``````

In [1]:

return i + n

``````

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

``````

In [2]:

``````
``````

In [3]:

``````
``````

Out[3]:

3

``````
``````

In [4]:

``````
``````

In [5]:

``````
``````

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 [ ]:

``````