Python Decorators

  • It’s a very powerful and useful tool in Python
  • It allows to add functionality to an existing function or class.
def make_pretty(func):
  def inner():
    print("Inner function")
    func()
  return inner

# Without decorator
def ordinary():
  print("Ordinary function")
pretty = make_pretty(ordinary)
pretty()

# With decorator(Using Closure)
@make_pretty
def ordinary():
  print("Ordinary function")
ordinary()

# Inner function
# Ordinary function
def deco(func):
    def inner_deco():
        print("this is the start of my fun")
        func()
        print("this is the end of my fun")
    return inner_deco
@deco
def test1():
    print(4+5)

test1()
# this is the start of my fun
# 9
# this is the end of my fun

Nested Decorators

def decor1(func): 
  def inner(): 
    x = func() 
    return x * x 
  return inner 

def decor(func): 
  def inner(): 
    x = func() 
    return 2 * x 
  return inner 

@decor1
@decor
def num(): 
  return 10
print(num()) 

# d1 = decor(num)
# d2 = decor1(d1)
# d2()
# Output: 400

Decorator without parameters

def outer(*args, **kwargs):
  print("Outer")
  def inner(func):		
    print("inner--", args[0])
    func()		
  return inner

@outer('Amrit')
def my_func():
  print("my function")

# -- fxn call not required

Decorator with parameters

def outer(func): 	# --
  print("Outer")
  def inner(*args, **kwargs): 	# --
    print("inner--", args[0])
    func(*args, **kwargs)		# --
  return inner

@outer
def my_func(var): # --
  print("my function")
my_func("Amrit") 	# --

# Both has same Answer

Returning Values From Decorated Functions

import functools

def Outer(func):
  @functools.wraps(func)
  def inner(*args, **kwargs):
    res = func(*args, **kwargs)
    print("Inner")
    return res
  return inner

@Outer
def sum(a,b):
  # print(a+b)
  return a+b

print(sum(1,2))

Decorators With Arguments –> args, kwargs

# Divide by 0 -- Error
def Outer(fxn):
  print("Outer")
  def inner(*args, **kwargs):
    if args[1] == 0:
      # raise Exception("b cant be 0")
      print("b can't be zero")
      return
    fxn(*args, **kwargs)
  return inner

@Outer
def main(a, b):
  print(a/b)

main(1,0)
# Calculate Execution time
# Using Decorator
# Also return value from fxn

import time
import functools

def Outer(func):
  @functools.wraps(func)
  def inner(*args, **kwargs):
    start = time.time()
    res = func(*args, **kwargs)
    end = time.time()
    print("Execution time: ", end-start)
    return res
  return inner

@Outer
def sum(a,b):
  time.sleep(2)
  # print(a+b)
  return a+b
print(sum(1,2))
# Repeat N times

import functools
def repeat(num_times):
  def decorator_repeat(func):
    @functools.wraps(func)
    def wrapper_repeat(*args, **kwargs):
      for _ in range(num_times):
        func(*args, **kwargs)
      # value = func(*args, **kwargs)
      # return value
    return wrapper_repeat
  return decorator_repeat

@repeat(num_times=4)
def greet(name):
    print(f"Hello {name}")

greet("Amrit-")

Reference