Decorators in Python

Shrayas (@shrayasr)


Recap on Functions

1. Functions can be defined inside functions


In [26]:
def say_something(thought):
    
    def _thought_makes_sense(thought):
        return True
    
    if _thought_makes_sense(thought):
        return thought
    
print say_something("That person stinks!")


That person stinks!

2. Functions are Lexically scoped


In [27]:
def say_something(thought):
    
    def _thought_makes_sense():
        print "HMMM \""+thought+"\"... I Wonder..."
        return True
    
    if _thought_makes_sense():
        return thought
    
print say_something("That person stinks!")


HMMM "That person stinks!"... I Wonder...
That person stinks!

3. Functions can be passed as arguments to other Functions


In [28]:
def say_something(thought, tone=None):
    return tone(thought)

def yell(thought):
    return thought.upper()

def whisper(thought):
    return "Shhh... " + thought.lower()

print say_something("where are you babu?", tone=yell)
print say_something("Lets go get an ice cream!", tone=whisper)


WHERE ARE YOU BABU?
Shhh... lets go get an ice cream!

4. Functions can be returned from functions


In [29]:
def mood_maker(mood):
    
    def happy(thought):
        return "^_^ " + thought + " ^_^"
    
    def sad(thought):
        return thought + " ... :'( sniff"
    
    moods = {
        "HAPPY": happy,
        "SAD": sad
    }
    
    return moods[mood.upper()]

say_happily = mood_maker("happy")
print say_happily("Hello!")

say_sadly = mood_maker("sad")
print say_sadly("I lost my favourite pen!")


^_^ Hello! ^_^
I lost my favourite pen! ... :'( sniff

Back to business

Lets add some masala to a function!


In [30]:
def masala_adder(func):
    
    def masalad_function():
        return "^#!^^@) [" + func() + "] !@&$**^&$"
        
    return masalad_function

def say_hi():
    return "Hii"
   
print say_hi()
masala_say_hi = masala_adder(say_hi)
print masala_say_hi()


Hii
^#!^^@) [Hii] !@&$**^&$

Masala, Decorators style!


In [31]:
@masala_adder
def say_hello():
    return "Hello"

say_hello()


Out[31]:
'^#!^^@) [Hello] !@&$**^&$'

Wait, what?


In [32]:
say_hello = masala_adder(say_hello)

Decorators are just syntactic sugar on a function that takes a function and returns a replacement function

A.K.A

Wrappers


In [33]:
# Define decorator here
def decorator(func):
    
    def new_func():
        print "before"
        func()
        print "after"
    
    return new_func

@decorator
def say_hello():
    print "Hello!"
    
say_hello()


before
Hello!
after

Lets make some owls!


In [34]:
def ears(func):    
    def ears_wrapper():
        owl_string = " /\\_/\\\n"
        owl_string += func()
        return owl_string
    
    return ears_wrapper

def eyes(func):    
    def eyes_wrapper():
        owl_string = " (O.O) \n"
        owl_string += func()
        return owl_string
    
    return eyes_wrapper

def body(func):    
    def body_wrapper():
        owl_string = " (= =)\n"
        owl_string += func()
        return owl_string
    
    return body_wrapper

def legs(func):    
    def legs_wrapper():
        owl_string = "  ^^^\n"
        owl_string += func()
        return owl_string
    
    return legs_wrapper

@ears
@eyes
@body
@legs
def owl():
    return "hoot hoot"

print owl()


 /\_/\
 (O.O) 
 (= =)
  ^^^
hoot hoot

In [35]:
owl = ears(eyes(body(legs(owl))))

Arguments to decorated functions


In [36]:
def decorator(func):
    def new_func(arg1, arg2):
        print "I R HAZ ARGZ! [%s, %s]" % (arg1, arg2)
        return func(arg1, arg2)
    return new_func

@decorator
def sum_of_squares(num1, num2):
    print num1*num1 + num2*num2
    
sum_of_squares(1,2)


I R HAZ ARGZ! [1, 2]
5

In [37]:
def logger(func):
    def decorated_function(*args, **kwargs):
        print "[INFO][Arguments]", args, kwargs
        return func(*args, **kwargs)
    return decorated_function

@logger
def sum_of_squares(num1, num2):
    print num1*num1 + num2*num2
    
@logger
def csv_reader(file_path, record_delimiter=",", line_delimiter="\n"):
    print "reading csv"
    
sum_of_squares(1,2)
csv_reader("/etc/init.d", record_delimiter=";")


[INFO][Arguments] (1, 2) {}
5
[INFO][Arguments] ('/etc/init.d',) {'record_delimiter': ';'}
reading csv

Exercise: Lets use decorators!

Let us write a small cache!


In [38]:
_cache = {}

"""
1_2 => 3
"""

def cache(func):
    
    def new_func(*args, **kwargs):
        
        key_str = ""
        for arg in args:
            key_str += str(arg) + "_"
        key_str = key_str[:-1]
        
        val = _cache.get(key_str)
        if not val:
            val = func(*args)
            _cache[key_str] = val
        return val
    
    return new_func

In [39]:
import time

@cache
def add_2_nos(a,b):
    time.sleep(2)
    return a+b

In [40]:
add_2_nos(1,2)


Out[40]:
3

In [41]:
add_2_nos(1,2)


Out[41]:
3

In [42]:
add_2_nos(10,20)


Out[42]:
30

In [43]:
add_2_nos(10,20)


Out[43]:
30

In [44]:
_cache


Out[44]:
{'10_20': 30, '1_2': 3}

Fin.