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() + "] [email protected]&$**^&$"
        
    return masalad_function

def say_hi():
    return "Hii"
   
print say_hi()
masala_say_hi = masala_adder(say_hi)
print masala_say_hi()
Hii
^#!^^@) [Hii] [email protected]&$**^&$

Masala, Decorators style!

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

say_hello()
Out[31]:
'^#!^^@) [Hello] [email protected]&$**^&$'

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.


boom