Shuan Chen

PhD Student in KAIST CBE

0%

Decorator

As a python learner, sometimes we see something like @cache before a function in other’s code in GitHub.

For example

1
2
3
4
@cache
def function():
###
reutrn output

This @ is called decorator. This article aims to explain what is @ and how useful is this @

Referece: Amo Chen ‘s blog

Understand “why decorator” from example

Let’s say we have three functions to calculate molecular similarity:

1
2
3
4
5
6
7
8
9
10
11
def similarity1(mol1, mol2):
###
return score1

def similarity2(mol1, mol2):
###
return score2

def similarity3(mol1, mol2):
###
return score3

If we want to know the run time of each function, we may need to edit the function one by one, such as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import time

def similarity1(mol1, mol2):
start_time = time.time()
###
end_time = time.time()
run_time1 = end_time-start_time
return score1, run_time1

def similarity2(mol1, mol2):
start_time = time.time()
###
end_time = time.time()
run_time2 = end_time-start_time
return score2, run_time2

def similarity3(mol1, mol2):
start_time = time.time()
###
end_time = time.time()
run_time3 = end_time-start_time
return score3, run_time3

when you run the code of these functions, if should give

1
2
3
print (similarity1(mol1, mol2)) # score1, runtime1
print (similarity2(mol1, mol2)) # score2, runtime2
print (similarity3(mol1, mol2)) # score3, runtime3

If we want to insepct the time for more functions, such operation is time-consuming and make the scripts very lengthy.

One for all - decorator

It would be great if there is a function that can be use on top of a function, which add the runtime as an additional ouput of each function. Yes, this is what decorator do: the function of function.

you define a decorator and put the extra operation you want to do in the wrapper function:

1
2
3
4
5
6
7
def decorator(function):
def wrapper(*args, **kwargs):
# do somthing before function
output = function(*args, **kwargs)
# do somthing after function
return output
return wrapper

When you write

1
2
3
4
@decorator
def function():
###
return output

it is equivalent to
1
function = decorator(function)

Hard to undertand? Let’s write a decorator called timeit to get the time of running a function:

1
2
3
4
5
6
7
8
9
10
11
12
import functools
import time

def timeit(function):
@functools.wraps(function) # keeps the info of original function
def wrapper(*args, **kwargs):
start_time = time.time()
output = function(*args, **kwargs)
end_time = time.time()
run_time = end_time-start_time
return output, run_time
return wrapper

BTW, args and kwargs are the abbreviations of “arguments” and “keyword arguments”.

and put this decorator in front of each function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@timeit
def similarity1(mol1, mol2):
###
return score1

@timeit
def similarity2(mol1, mol2):
###
return score2

@timeit
def similarity3(mol1, mol2):
###
return score3

Then the output will be exactly same with the previous outputs:

1
2
3
print (similarity1(mol1, mol2)) # score1, runtime1
print (similarity2(mol1, mol2)) # score2, runtime2
print (similarity3(mol1, mol2)) # score3, runtime3

Amazing!