1. Home
  2. Docs
  3. Python 201
  4. Decorators in Python

Decorators in Python

 
In this lesson you will learn about decorators.

 

What is a Decorator ?

 
A decorator is a function, that creates a wrapper function around

another function.

The wrapper function added by Decorator,

works the same as original function,

but adds some kind of extra processing.

In Python decorators are implemented as callables,

which take a callable and return callable.

The above statement, might be little confusing,

But wait a minute we will get there.

For now just think of decorator as a function which takes

function as argument and return function.

Which we have already seen in Closures.

 

Function decorators

 
syntax:

 

@my_decorator
def my_function()

 
 

Here my_decorator is decorator applied to my_function,

a special symbol ‘@’ is used to apply decorator to function.

@my_decorator is also called as syntantic sugar,

which is same as

 

my_function = my_decorator(my_function)

 
 
Let’s look at an example,

Below we have three functions, which takes

two numbers and process them.

 

def add(x,y):
	
	return x + y
	
def multiple(x,y)
	
	return x * y
	
def raise_to(x,y):
	
	return x ** y
	

 
 

When we call add() function.

 

add(10,20)

output:

30

 
 

The output is as expected.

What if the user doesn’t provide numbers

 


add(10, 'JLP')

output:

TypeError: unsupported operand type(s) for +: 'int' and 'str'

 
 

We get a TypeError exception.

So its becomes user responsibility to provide correct values.

One way of fixing is using try-exception block

 

def add(x,y):

    try:
        return x + y
		
    except TypeError:
	
        print("Invalid arguments")
		
		
add(10, "JLP")		
		
output:

Invalid arguments

 
 

Another way of ensuring type is, by checking type of

inputs before processing in function.

 

	
def add(x,y):

    if type(x ) == int and type(y) == int:
	
            return x + y
    else:
	
        print("Invalid arguments")
		
add(10, "JLP")

output:

Invalid arguments

add(10, 20)

output:

30
	

 
 

But these two approach are not scalable,

Suppose you have 200 such functions in your code base,

and its hell lot of work to modify each function to

put try-except or type check code.

That’s where decorators come to our rescue.

Let’s see how decorators make our life easy.

 

First decorator function

 
A decorator function, which validates,

input parameters as integers.

 

def ensure_int(f):
    
    def wrapper(x,y):
        
        if type(x) == int and type(y) == int:
            
            return f(x,y)
        
        else:
            
            print("Invalid arguments")
            
    return wrapper


 
 

Here ensure_int(f) is decorator function, which only

takes callables, in this case, function ‘f’.

It creates wrapper() function, which takes

same arguments as function ‘f’.

It validates the input parameters as integer and

calls the original function ‘f’, if parameters are integers,

otherwise it prints a message.

After this processing, ensure_int returns wrapper function.

Now let’s use this decorator on a function.

 

Decorating function

 

We will go back to, original definitions three functions.
 
Ex 1:

 

def add(x,y):

    return x + y


def multiple(x,y):

    return x * y

def raise_to(x,y):

    return x ** y

 
 

applying decorator to add() function
 

@ensure_int
def add(x,y):

    return x + y
	
	
# call add with invalid inputs

add(10,'JLP')

output:

Invalid arguments

# call add with valid inputs

add(10,20)


output:

30

 
 
Works as expected right ? that’s the power of decorators.

@ensure_int this syntax can also be written as

before calling the function

 

add = ensure_int(add)

add(10,'JLP')

output:

Invalid arguments


add(10,20)

output:

30

 
 

How decorator works

 
@ is special symbol denotes a decorator.

Whenever Python iterpreter sees @ symbol it identifies

that a decorator is being applied, and

step 1: it compiles add() in to a function object.

step 2: Python then passes compiled function object to
def ensure_int(f),

By design ensure_int takes and return function.

step 3: Python binds the return value to original function name.

Applying decorator to other functions

 

@ensure_int
def add(x,y):

    return x + y

@ensure_int
def multiple(x,y):

    return x * y

@ensure_int
def raise_to(x,y):

    return x ** y
	

 
 
We will check, how raise_to function works,

after applying decorator.

 


# call raise_to with invalid inputs

raise_to(4,'JLP')

output:

Invalid arguments

# call raise_to with valid inputs

raise_to(4,3)
	
64

 
 

It works as expected, right?

Now let’s look at another example.
 
Ex 2:
 
Another simple decorator example which validates,

input as string object.

 

def validate_string(func):
    
    def wrapper(arg):
        
        if type(arg) != str:
            
            raise ValueError(repr(arg)+ " is not a string")
            
        return func(arg)
    
    return wrapper


@validate_string
def string_input(arg):
    
    print(len(arg),arg)
    
string_input('justlearnpython')


output:

15 justlearnpython

 
 

validate_string() is decorator creates a wrapper function,

which validates input as string, raises an ValueError Exception

What if the input is not a string ?.

 

# call string_input with invalid input

string_input([1,2,3])

output:

ValueError: [1, 2, 3] is not a string

 
 

Now, you know, how decorator works and how to use them.

As a general practice, decorators define wrapper function,

which takes any number of positional arguments and keyword arguments.
 

syntax:
 
This is just an example of decorator, its not that useful.

 

def my_decorator(func):

	def wrapper(*args, **kwargs):
		
		return func(*args, **kwargs)
				
	return wrapper

 
 
Notice wrapper function signature.

 

def wrapper(*args, **kwargs):
	
	return func(*args, **kwargs)

 
 

It takes *args, **kwargs, which makes it more generic.

Below we will create a little useful decorator, which counts

the number of times a function is called.
 
Ex 3:
 
An example to count how many times the function is called.

 

def count(func):
    
    def wrapper(*args, **kwargs):

        wrapper.counter += 1

        return func(*args,**kwargs)
    
    wrapper.counter = 0

    return wrapper

 
 

Here count() is the decorator function,

creates a wrapper around func(),

and it has counter which incremented each time

the func() is called.

Below we have three methods, which takes different number

of arguments.

We will apply count() decorator to each one of them.

These function don’t have any implementation, this example is to

demonstrate, how variable number of arguments are handled in

decorator functions.

 

	
@count
def my_func1():

    pass

@count
def my_func2(x,y):

    pass

@count
def my_func3(*args,**kwargs):

    pass
	

output:

# call my_func1 two times

my_func1()

my_func1()
	

# check the counter

my_func1.counter

output:

2

 
 

Now let’s call my_func3() with different types of arguments.

 

# call with multiple integer values

my_func3(1,2,3,4)


# call with a string 

my_func3("1,2,3,4")


# call with an integer

my_func3(2)


# check the counter

my_func3.counter

output:

3

 
 

Notice the my_func3() called 3 times.

This example shows, the decorator can be applied to any function.

Because the wrapper function takes *args and **kwargs.

It can handle any type function.

However there is caveat, when you apply decorator to a function.

We will understand this with a simple example.
 

wraps decorator

 
Let’s take a simple decorator example

 

def my_decor(f):

    def wrapper():
	
        return f()
		
    return wrapper


def hello():
    ''' Helllo function, says hello'''

    print("Hello There!")

 
 

Here we have simple decorator my_decor(),

it just creates a wrapper() around f() and returns wrapper.

And we have a hello() function, which prints ‘Hello There!’

and it has a docstring.

When we create function each function object has its own

metadata, like __name__, __doc__ etc.

Metadata is used by functions like help() or dir().

Below we are investigating, what happens to function metada,

when we apply decorator to a function.

Let’s check the __name__ attribute before applying the decorator.

 

# check the __name__

hello.__name__

output:

'hello'

# check the __doc__

hello.__doc__

output:

' Helllo function, says hello'


Now apply decorator to hello() function.

 


hello = my_decor(hello) # same as  @my_decor

# check the __name__

hello.__name__

output:

'wrapper'

#check the __doc__

hello.__doc__

output:

#nothing get printed

 
 

Notice after applying decorator the metadata of

hello() is changed and, it is replaced with wrapper() metadata.

This is not correct right? wrapper() has to maintain hello()

as it is.
 

How to fix this ?
 
This can be fixed by assigning wrapper attributes,

with hello() function attributes.

 

def my_decor(f):
    
    def wrapper():
	
        return f()
		
    wrapper.__name__ = f.__name__
	
    wrapper.__doc__ = f.__doc__
	
	    return wrapper
		
@my_decor
def hello():
    ''' Helllo function, says hello'''
	
    print("Hello There!")


 
 
Now let’s again check the __name__ and __doc__.

 

# check __name__

hello.__name__

output:

'hello'


# check __doc__

hello.__doc__

output:

' Helllo function, says hello'

 
 

This works fine, but its kind of ugly code.

However there is an elegant way of doing it,

by using functools wraps() decorator.

Let’s use that and see.

 

from functools import wraps

def my_decor(f):

    @wraps   
    def wrapper():

		return f()
		
    return wrapper

def hello():
    ''' Helllo function, says hello'''
	
    print("Hello There!")


 
 

Let’s again check the __name__ and __doc__

 

# check __name__

hello.__name__

output:

'hello'


# check __doc__

hello.__doc__

output:

' Helllo function, says hello'

 
 
Works perfectly, right?

The @wraps decorator applied on wrapper function,

which ensures metadata of original function copied

properly during wrapper creation.
 

What else can be decorator?

 

Till now we have seen function as decartor,

which is most common use of decorator.

However class and instance can also be decorators.

 

classes as decorators

 

A class can be decorator, if it is callable,

That is, if class has implemented __call__()

special method.

Like function decorator, class decorator takes and return

a callable object.
 

syntax:

 

 class MyCallDecorator(object):
    
    def __init__(self,f):
        pass
    
       def __call__(self, *args,**kwargs):
	   
		return self.f(*args,**kwargs)
        
        pass
    

 
 

Applying class decorator to a function.

 

@MyClassDecorator
def my_func():

    pass
	

 
 

Notice init mehtod takes original function as argument.

__init__(self,f)

and __call__ function works as a wrapper function.

With this knowledge let’s create a class decorator.

 

class MyClassDecorator(object):
    
    def __init__(self, f):
        
        self.f = f
        
        self.call_count = 0
        
     
        
    def __call__(self, *args,**kwargs):
        
        self.call_count += 1
        
        return self.f(*args,**kwargs)
		
		

 
 
In this example, MyCallDecorator is class decorator,

__ini__ function takes ‘f’ original function as argument.

call_count is a instance variable, to track function calling.

__call__(*args, **kwargs) method is overridden,

and it acts as a wrapper function, it increments call_count

each time ‘f’ is called, and returns the ‘f’.

 
How to use class decorator?
 
There is not difference in using function decorators

and class decorators, see below.

 

 

@MyClassDecorator
def my_func():

    print("Hello There!")

 
 

We have to check whether decorator doing its job or not.

For this call the my_func() two times.

 

 my_func()
 my_func()
 
 

 
 

Check whether decorator is working ?

 

 
 my_func.call_count
  
 output:
  
 Hello There!
 Hello There!
 
 2
 
 

 
 

We got the call_count as 2, as expected.

Which confirms, the class decorator is applied to my_func().

Now quickly look in to an instance decorator.

 

Instance as decorator

 
A class instance can also be a decorator.

Lets look at an example of instance decorator.

With this decorator, we will try to toggle debug information.

 

 
class DebugInfo(object):

    def __init__(self):

		self.debug = True
        
    def __call__(self, f):
        
        def wrapper(*args,**kwargs):
            
            if self.debug:
                print("Function name:",f.__name__)
                
            return f(*args,**kwargs)

        return wrapper
		

 
 

The code is similar to class decorator, the difference here

is we are not passing, ‘f’ to __init__

We have ‘debug’ instance variable, which is used

for printing some useful debug info for user.

__call__ method takes a ‘f’ as argument,

and implements a wrapper function, which prints function
name and returns original function ‘f’

How to use instance decorators ?

First create an instance of class, and use instance reference as decorator.

 

dbg_inst = DebugInfo()

@dbg_inst
def gen_numbers(n):
    
    print( list(range(n)))


#Call function
    
gen_numbers(4)

output:

Function name: gen_numbers
[0, 1, 2, 3]

#Call function

gen_numbers(3)

output:

Function name: gen_numbers

[0, 1, 2]

 
 

The real use of this decorator is to enable and

disable debug information, when the user wants.

Let’s disable debug information by using instance variable,

note ‘debug’ value is used in wrapper function.

 

dbg_inst.debug = False

#Call function

gen_numbers(3)

output:

[0, 1, 2]

 
 

Notice the output, debug information is disabled.

 

Using multiple decorators

 
We can use multiple decorarors in combination.

It will look something like below.

 

@my_dec3
@my_dec2
@my_dec1
def my_function()

 
 
The decorators are applied in reverse order,

first my_dec1 is applied, and output of my_dec1 is

supplied as argument to my_dec2, my_dec2 return value

is supplied to my_dec3.

The final value is again binded to original function

reference.

They work same ways as single decorator.