Tags: python
In python, everything is an object. So functons are also objects which can be passed around. A decorator is a function that receives a function and returns a function.
Let's say we have two simple functions to add and multiply numbers.
def multiply_me(a, b): | |
return a * b | |
def add_me(a, b): | |
return a + b | |
print(multiply_me(3, 2)) | |
print(add_me(3, 2)) |
This will print
6
5
Now lets say we want to print execution
time in both these functions. We could do something like this
from time import time, sleep | |
def multiply_me(a, b): | |
t = time() | |
res = a * b | |
sleep(2) | |
print("{} took {}".format(multiply_me.__name__, time() - t)) | |
return res | |
def add_me(a, b): | |
t = time() | |
res = a + b | |
sleep(2) | |
print("{} took {}".format(add_me.__name__, time() - t)) | |
return res | |
print(multiply_me(3, 2)) | |
print(add_me(3, 2)) |
This will print
multiply_me took 2.00507378578
6
add_me took 2.00509095192
5
Here we are repeating our logic for logging execution time. This can be solved by using decorators.
Let's create a wrapper function called timeme
that will do the following-
function
as an argumentfunction
def timeme(func): | |
def wrapper(*args, **kwargs): | |
t = time() | |
res = func(*args, **kwargs) | |
print("{} took {}".format(func.__name__, time() - t)) | |
return res | |
return wrapper |
Next we can decorate
our functions using syntatic sugar like this
@timeme | |
def multiply_me(a, b): | |
return a * b | |
@timeme | |
def add_me(a, b): | |
return a + b |
This is similar to the following
multiply_me = timeme(multiply_me)
add_me = timeme(add_me)
Lastly runnig the functions will be like
print(multiply_me(3, 2))
print(add_me(3, 2))
This will print
multiply_me took 2.00506806374
6
add_me took 2.00509810448
5
Lets define a function which depends on type of argument.
from dataclasses import dataclass | |
@dataclass | |
class Dog: | |
bark : str | |
def process_data(x): | |
if type(x) == str: | |
print("str executed %s" % x) | |
elif type(x) == int: | |
print("int executed %s" % x) | |
elif type(x) == list: | |
print("list executed %s" % x) | |
elif type(x) == Dog: | |
print("Dog executed %s" % x.bark) | |
else: | |
print("Default executed %s" % x) |
We can invoke the function with different arguments.
process_data(set())
process_data("string")
process_data(123)
process_data(Dog("bowbow"))
This will print
Default executed set()
str executed string
int executed 123
Dog executed bowbow
This coding style becomes hard to read as the number of cases increases. We can use singledispatch
decorator.
from functools import singledispatch | |
@singledispatch | |
def process_data(x): | |
print("Default executed %s" % x) | |
@process_data.register(str) | |
def _(x): | |
print("str executed %s" % x) | |
@process_data.register(int) | |
def _(x): | |
print("int executed %s" % x) | |
@process_data.register(list) | |
def _x(x): | |
print("list executed %s" % x) | |
@process_data.register(Dog) | |
def _x(x): | |
print("Dog executed %s" % x.bark) |
This will print the same result
Default executed set()
str executed string
int executed 123
Dog executed bowbow