Demystifying Decorators
AkulMehra
Akul Mehra
🇮🇳
Agenda
- Intro to functions
- What are decorators
- Understanding how they work
- Useful Examples
- Decorators with parameters
- Class Decorators
- Flask Decorators
Intro to Functions
- Reusable Code (DRY principle)
- Useful Block of Organized Code
- Easiear to read
- Take inputs and produce outputs
def func():
pass
Examples
def hello():
''' Basic example of function. '''
return 'Hello World'
print(hello())
# Output: Hello World
def add(a, b):
''' take a and b as args, add them and
return the result. '''
return a + b
print(add(2,5))
# Output: 7
Everything in Python is an Object
Functions are too !
As an object, you can assign the function to a variable
def hello():
print 'Hello'
hello()
# prints 'Hello'
hi = hello
hi()
# prints 'Hello'
Functions are too !
They can be passed around like other values (strings, integers, objects, etc.).
def add(a, b):
return a + b
def apply(func, x, y):
return func(x, y)
print apply(add, 2, 5)
# outputs: 7
Functions are too !
Function can be defined inside another function. That means a function can return another function.
def print_me(word):
def inner():
print(word)
return inner
f = print_me('Hello World!')
f()
# prints 'Hello World!'
This is called a closure
Decorators are also closures.
Objects are data with methods attached, Closures are functions with data attached.
Decorators
Instead, they take function object as an argument and return function object as return value.
It extends the behavior of the latter function without explicitly modifying it
Decorators
-
Decorators wrap Functions
-
Add Functionality
-
Modify behaviour
-
Diagnostics (timing etc)
Decorators
def my_decorator(original_function):
def new_function(*args, **kwargs):
print 'Entering', original_function.__name__
result = original_function(*args, **kwargs)
print 'Exiting', original_function.__name__
return result
return new_function
@my_decorator
def my_func():
pass
Decorators
# Syntactic Sugar
@my_decorator
def my_func():
pass
Under the hood
is same as writing:
def my_func():
pass
my_func = my_decorator(my_func)
*args and **kwargs enable the function to accept any number of postional and keyword arguments.
*args is a list
**kwargs is a dictionary
*args & **kwargs
*args becomes args[0], args[1], args[2] ...
**kwargs becomes key1=kwargs[key1], key2=kwargs[key2] ...
*args & **kwargs
Timer decorator
import time
def timeit(func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
time_taken = time.time() - start_time
print('Time taken is ', time_taken)
return result
return inner
@timeit
def sleep(t=1):
print('sleeping for ', t)
time.sleep(t)
sleep()
# sleeping for 1
# Time taken is 1.005326747894287
sleep(2)
# sleeping for 2
# Time taken is 2.004933118820190
Examples
Memoization decorator
import time
def memo(func):
''' Decreases the time taken by func from
exponential to linear time. '''
cache = {}
def inner(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return inner
@memo
def fibo(n):
if n < 2:
return n
return fibo(n - 1) + fibo(n - 2)
start_time = time.time()
print(fibo(40))
print('Time taken: ', time.time() - start_time)
# Without memoization: Time taken is 54.21900486946106s
# With memoization: Time taken is 0.00011396408081054688s
Examples
Counter decorator
def count(func):
def inner(*args, **kwargs):
inner.counter += 1
return func(*args, **kwargs)
inner.counter = 0
return inner
@count
def myfunc():
pass
Examples
In [1]: from counter import *
In [2]: myfunc()
In [3]: myfunc()
In [4]: myfunc()
In [5]: myfunc.counter
Out[5]: 3
def polynomial_creator(a, b, c):
def polynomial(x):
return a * x**2 + b * x + c
return polynomial
p1 = polynomial_creator(2, 3, -1)
x = 2
print('x: ', x, 'p(x): ', p1(x))
# Output:
# x: 2, p(x): 13
# 2 * 2**2 + 3 * 2 + (-1) = 13
Examples
POLYNOMIAL FUNCTIONS
Chaining decorators
Bold and Italic decorators
def makebold(fn):
def wrapped():
return "<b>" + fn() + "</b>"
return wrapped
def makeitalic(fn):
def wrapped():
return "<i>" + fn() + "</i>"
return wrapped
@makebold
@makeitalic
def hello():
return "hello world"
print(hello()) ## Outputs: "<b><i>hello world</i></b>"
Chaining decorators
Bold and Italic decorators
@makebold
@makeitalic
def hello():
return "hello world"
def hello():
return "hello world"
hello = makebold(makeitalic(hello))
The above syntactic sugar statement is same as:
Inspecting decorators
def shout(func):
def inner(*args, **kwargs):
''' Inner func modifies the behaviour of original func '''
print('BEFORE SHOUTING')
result = func(*args, **kwargs)
print('AFTER SHOUTING')
return result
return inner
@shout
def hello(name='World'):
''' returns hello with name argument '''
print('Hello', name)
hello('HDE Team')
# Output
BEFORE SHOUTING
Hello HDE Team
AFTER SHOUTING
Inspecting decorators
In [1]: from shout import *
BEFORE SHOUTING
Hello HDE Team
AFTER SHOUTING
In [2]: hello.__name__
Out[2]: 'inner'
In [3]: hello.__doc__
Out[3]: ' Inner func modifies the behavior of original func '
Using decorators override the metadata of the original function.
Inspecting decorators
Using decorators override the metadata of the original function.
Using functools.wraps copies the metadata like name and docstring of the original function inside the decorator function
Inspecting decorators
from functools import wraps
def shout(func):
@wraps(func)
def inner(*args, **kwargs):
''' Inner func modifies the behaviour of original func '''
print('BEFORE SHOUTING')
result = func(*args, **kwargs)
print('AFTER SHOUTING')
return result
return inner
@shout
def hello(name='World'):
''' returns hello with name argument '''
print('Hello', name)
hello('HDE Team')
Using wraps from functools library
Inspecting decorators
Using wraps from functools library
In [1]: from shout import *
BEFORE SHOUTING
Hello HDE Team
AFTER SHOUTING
In [2]: hello.__name__
Out[2]: 'hello'
In [3]: hello.__doc__
Out[3]: ' returns hello with name argument '
Decorators with argument
def skipIf(condition, message):
def dec(func):
def inner(*args, **kwargs):
if not condition:
return func(*args, **kwargs)
else: # skip the func
print(message)
return inner
return dec
@skipIf(False, 'Do not skip this')
def enter():
print('In the Enter function')
@skipIf(True, 'Skipped this function')
def exit():
print('In the Exit function')
enter()
exit()
# Output
In the Enter function
Skipped this function
Decorators with argument
@skipIf(False, 'Do not skip this')
def enter():
print('In the Enter function')
enter()
def enter():
print('In the Enter function')
enter = skipIf(False, 'Do not skip this')(enter)
is same as below statement
skipIf creates a decorator named dec and returns it, which is called with enter function as an argument
Decorators with argument
def make(word):
def dec(fn):
def wrapped():
return "<" + word + ">" + fn() + "</" + word + ">"
return wrapped
return dec
@make('b') # To make bold
@make('i') # To make italic
def hello():
return "hello world"
print(hello()) # Outputs: "<b><i>hello world</i></b>"
Make Generic ( Bold & Italic ) Decorators
Text
Class Decorators
def decorate(func):
''' decorate functions '''
def inner(*args, **kwargs):
print('*' * 20)
result = func(*args, **kwargs)
print('*' * 20)
return result
return inner
def decorate_class(cls):
'''
Takes class object as input and apply decorate
func to all the callable functions.
'''
for k, v in vars(cls).items():
if callable(v):
setattr(cls, k, decorate(v))
return cls
Class Decorators
@decorate_class
class Area:
'''
Functions to calculate area.
'''
def triangle(self, b, h):
print('Area of right triangle: ', 0.5 * b * h)
def square(self, s):
print('Area of square: ', s * s)
def rectangle(self, l, b):
print('Area of rectangle: ', l * b)
Class Decorators
In [1]: from decorate_class import *
In [2]: ar = Area()
In [3]: ar.triangle(10, 15)
********************
Area of right triangle: 75.0
********************
In [4]: ar.square(10)
********************
Area of square: 100
********************
In [5]: ar.rectangle(10,15)
********************
Area of rectangle: 150
********************
Output
Flask uses decorators extensively
class App:
route_functions = {}
def route(url_pattern):
def wrap(f):
route_functions[url_pattern] = f
return f
return wrap
@app.route decorator
@app.route(“/”)
def index():
return “Hello world”
index = app.route(“/”)(index) = wrap(index) = index
# index is added to the route_functions mapping with url pattern.
route_functions['/'] = index
Flask uses decorators extensively
def login_required(f):
def wrap(*args, **kwargs):
# if user is not logged in, redirect to login page
if not request.headers["authorization"]:
return redirect("login page")
# get user via some ORM system
user = User.get(request.headers["authorization"])
# make user available down the pipeline via flask.g
g.user = user
# finally call f. f() now haves access to g.user
return f(*args, **kwargs)
return wrap
@login_required decorator
@app.route(“/”)
@login_required
def index():
return “Hello world”
QUESTIONS ?
AkulMehra
akul08
akul08
Thank You
AkulMehra
akul08
akul08
Demystifying Decorators
By Akul Mehra
Demystifying Decorators
A brief Introduction on Decorators in Python.
- 818