Python Decorators

Download as pdf or txt
Download as pdf or txt
You are on page 1of 24

Python Decorators

Decorators in Python are special functions which adds additional functionality to an


existing function or code.

For example, you had a white car with basic wheel setup and a mechanic changes
the color of your car to red and fits alloy wheels to it then the mechanic decorated
your car, similarly a decorator in python is used to decorate(or add functionality or
feature) to your existing code.

In [20]:

1 # some function
2 def first(msg):
3 print(msg)
4
5 # second function
6 def second(func, msg):
7 func(msg)
8
9 # calling the second function with first as argument
10 second(first, "Hello!")

Hello!

While in the example above the function second took the function first as an argument
and used it, a function can also return a function.

When there is nested functions(function inside a function) and the outer function
returns the inner function it is known as Closure in python.

Sample 1 - Adding $ to the return value from price()


function
If we want a function that does prefix '$', the decorator can help us:
In [22]:

1 def dollar(fn):
2 def new(*args):
3 return '$' + str(fn(*args))
4 return new
5
6 @dollar
7 def price(amount, tax_rate):
8 return amount + amount*tax_rate
9
10 print(price(100,0.1))
11
12 @dollar
13 def hello(number):
14 return number*2
15
16 print(hello(30))

$110.0
$60

1 The dollar decorator function takes the price() function,


and returns enhanced the output from the original price()
after modifying the inner working.
2
3 Note that the decorator enables us to do it without making
any changes on the price() function itself.
4
5 So, decorator works as a wrapper, modifying the behavior of
the code before and after a target function execution,
without the need to modify the function itself, enhancing
the original functionality.
In [28]:

1 def heading(f):
2 def wrapped():
3 return '<H1>' + f() + '</H1>'
4 return wrapped
5
6 def bold(f):
7 def wrapped():
8 return '<b>' + f() + '</b>'
9 return wrapped
10
11 def italic(f):
12 def wrapped():
13 return '<i>' + f() + '</i>'
14 return wrapped
15
16
17 @heading
18 @bold
19 @italic
20 def welcome():
21 return 'Welcome to Decorator'
22
23 print(welcome())

<H1><b><i>Welcome to Decorator</i></b></H1>

Welcome to Decorator
1 # Using Decorators in Python
2
3 A decorator gives a function a new behavior without
changing the function itself. A decorator is used to add
functionality to a function or a class. In other words,
python decorators wrap another function and extends the
behavior of the wrapped function, without permanently
modifying it.
4
5 Now, Let's understand the decorators using an example:-
In [1]:

1 # a decorator function
2 def myDecor(func):
3 # inner function like in closures
4 def wrapper():
5 print("Modified function")
6 func()
7 return wrapper
8
9
10 def myfunc():
11 print('Hello!!')
12
13 # Calling myfunc()
14 myfunc()
15
16 # decorating the myfunc function
17 decorated_myfunc = myDecor(myfunc)
18
19 # calling the decorated version
20 decorated_myfunc()

Hello!!
Modified function
Hello!!

In the code example above, we have followed the closure approach but instead of
some variable, we are passing a function as argument, hence executing the function
with some more code statements.

We passed the function myfunc as argument to the function myDecor to get the
decorated version of the myfunc function.

Now rather than passing the function as argument to the decorator function, python
provides us with a simple way of doing this, using the @ symbol.
In [3]:

1 # using the decorator function


2 @myDecor
3 def myfunc():
4 print('Hello!!')
5
6 # Calling myfunc()
7 myfunc()

Modified function
Hello!!

In the code example above, @myDecor is used to attach the myDecor() decorator to
any function you want.

So when we will call myfunc(), instead of execution of the actual body of myfunc()
function, it will be passed as an argument to myDecor() and the modified version of
myfunc() is returned which will be executed.

So, basically @ is used to attach any decorator with name Decorator_name to any
function in python programming language.

Decorators with arguments


Till now we have seen the use of decorators to modify function that hasn't used any
argument. Now, let's see how to use argument with a function which is to be
decorated.

For this, we are going to use args and *kwargs as the arguments in the inner function
of the decorator.

The *args in function definition is used to pass a variable number of arguments to any
function. It is used to pass a non-keyworded, variable-length argument list.

The **kwargs in function definitions is used to pass a keyworded, variable-length


argument list. We use the name kwargs with the double star. The reason is that the
double star allows us to pass through keyword arguments (and any number of them).
In [4]:

1 def myDecor(func):
2 def wrapper(*args, **kwargs):
3 print('Modified function')
4 func(*args, **kwargs)
5 return wrapper
6
7 @myDecor
8 def myfunc(msg):
9 print(msg)
10
11 # calling myfunc()
12 myfunc('Hey')
13
14

Modified function
Hey

In the example, the myfunc() function is taking an argument msg which is a message
that will be printed.

The call will result in the decorating of function by myDecor decorator and argument
passed to it will be as a result passed to the args of wrapper() function which will
again pass those arguments while calling myfunc() function.

And finally, the message passed will be printed after the statement 'Modified
function'.

Chaining the Decorators


We can use more than one decorator to decorate a function by chaining them. Let's
understand it with an example:-
In [5]:

1 # first decorator
2 def star(f):
3 def wrapped():
4 return '**' + f() + '**'
5 return wrapped
6
7 # second decorator
8 def plus(f):
9 def wrapped():
10 return '++' + f() + '++'
11 return wrapped
12
13 @star
14 @plus
15 def hello():
16 return 'hello'
17
18 print(hello())

**++hello++**

In [ ]:

1 class MyClass:
2 def method(self):
3 return 'instance method called', self
4
5 @classmethod
6 def classmethod(cls):
7 return 'class method called', cls
8
9 @staticmethod
10 def staticmethod():
11 return 'static method called'

Practical use of Decorators


Decorators are very often used for adding the timing and logging functionalities to the
normal functions in a python program. Let's see one example where we will add the
timing functionalities to two functions:
In [6]:

1 import time
2
3 def timing(f):
4 def wrapper(*args, **kwargs):
5 start = time.time()
6 result = f(*args,**kwargs)
7 end = time.time()
8 print(f.__name__ +" took " + str((end-start)*1000) +
9 return result
10 return wrapper
11
12 @timing
13 def calcSquare(numbers):
14 result = []
15 for number in numbers:
16 result.append(number*number)
17 return result
18
19 @timing
20 def calcCube(numbers):
21 result = []
22 for number in numbers:
23 result.append(number*number*number)
24 return result
25
26 # main method
27 if __name__ == '__main__':
28 array = range(1,100000)
29 sq = calcSquare(array)
30 cube = calcCube(array)

calcSquare took 16.63804054260254 mil sec


calcCube took 25.146961212158203 mil sec
In the above example, we have created two functions calcCube and calcSquare
which are used to calculate square and cube of a list of numbers respectively. Now,
we want to calculate the time it takes to execute both the functions, for that we have
defined a decorator timing which will calculate the time it took in executing both the
functions.

Here we have used the time module and the time before starting a function to start
variable and the time after a function ends to end variable. f.name gives the name of
the current function that is being decorated. The code range(1,100000) returned a list
of numbers from 1 to 100000.

So, by using decorators, we avoided using the same code in both the functions
separately (to get the time of execution). This helped us in maintaining a clean code
as well as reduced the work overhead.

In [11]:

1 # PythonDecorators/my_decorator.py
2 class my_decorator(object):
3
4 def __init__(self, f):
5 print("inside my_decorator.__init__()")
6 f() # Prove that function definition has completed
7
8 def __call__(self):
9 print("inside my_decorator.__call__()")
10
11 @my_decorator
12 def aFunction():
13 print("inside aFunction()")
14
15 print("Finished decorating aFunction()")
16
17 aFunction()

inside my_decorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside my_decorator.__call__()

In [8]:

1 # PythonDecorators/decorator_with_arguments.py
2 class decorator_with_arguments(object):
3
4 def __init__(self, arg1, arg2, arg3):
5 """
6 If there are decorator arguments, the function
7 to be decorated is not passed to the constructor!
8 """
9 print("Inside __init__()")
10 self.arg1 = arg1
11 self.arg2 = arg2
12 self.arg3 = arg3
13
14 def __call__(self, f):
15 """
16 If there are decorator arguments, __call__() is only c
17 once, as part of the decoration process! You can only
18 it a single argument, which is the function object.
19 """
20 print("Inside __call__()")
21 def wrapped_f(*args):
22 print("Inside wrapped_f()")
23 print("Decorator arguments:", self.arg1, self.arg2
24 f(*args)
25 print("After f(*args)")
26 return wrapped_f
27
28 @decorator_with_arguments("hello", "world", 42)
29 def sayHello(a1, a2, a3, a4):
30 print('sayHello arguments:', a1, a2, a3, a4)
31
32 print("After decoration")
33
34 print("Preparing to call sayHello()")
35 sayHello("say", "hello", "argument", "list")
36 print("after first sayHello() call")
37 sayHello("a", "different", "set of", "arguments")
38 print("after second sayHello() call")

Inside __init__()
Inside __call__()
After decoration
Preparing to call sayHello()
Inside wrapped_f()

Decorator arguments: hello world 42


sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
After f(*args)
after second sayHello() call
In [10]:

1 # PythonDecorators/entry_exit_class.py
2 class entry_exit(object):
3
4 def __init__(self, f):
5 self.f = f
6
7 def __call__(self):
8 print("Entering", self.f.__name__)
9 self.f()
10 print("Exited", self.f.__name__)
11
12 @entry_exit
13 def func1():
14 print("inside func1()")
15
16 @entry_exit
17 def func2():
18 print("inside func2()")
19
20 func1()
21 func2()

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
In [9]:

1 # PythonDecorators/entry_exit_function.py
2 def entry_exit(f):
3 def new_f():
4 print("Entering", f.__name__)
5 f()
6 print("Exited", f.__name__)
7 return new_f
8
9 @entry_exit
10 def func1():
11 print("inside func1()")
12
13 @entry_exit
14 def func2():
15 print("inside func2()")
16
17 func1()
18 func2()
19 print(func1.__name__)

Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
new_f
In [7]:

1 # PythonDecorators/decorator_function_with_arguments.py
2 def decorator_function_with_arguments(arg1, arg2, arg3):
3 def wrap(f):
4 print("Inside wrap()")
5 def wrapped_f(*args):
6 print("Inside wrapped_f()")
7 print("Decorator arguments:", arg1, arg2, arg3)
8 f(*args)
9 print("After f(*args)")
10 return wrapped_f
11 return wrap
12
13 @decorator_function_with_arguments("hello", "world", 42)
14 def sayHello(a1, a2, a3, a4):
15 print('sayHello arguments:', a1, a2, a3, a4)
16
17 print("After decoration")
18
19 print("Preparing to call sayHello()")
20 sayHello("say", "hello", "argument", "list")
21 print("after first sayHello() call")
22 sayHello("a", "different", "set of", "arguments")
23 print("after second sayHello() call")

Inside wrap()
After decoration
Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

In [ ]:

1 #/Users/SurendraMac/Python27
In [ ]:

1 # %load trace.py
2 #trace.py
3 def trace( aFunc ):
4 """Trace entry, exit and exceptions."""
5 def loggedFunc(*args, **kw ):
6 print("enter", aFunc.__name__)
7 try:
8 result= aFunc(*args,**kw )
9 except Exception, e:
10 print("exception",aFunc.__name__, e)
11 raise
12 print("exit", aFunc.__name__)
13 return result
14 loggedFunc.__name__= aFunc.__name__
15 loggedFunc.__doc__= aFunc.__doc__
In [ ]:

1 # %load trace_client.py
2 ##Here's a class which uses our @trace decorator.
3 ##trace_client.py
4 from trace1 import trace
5 class MyClass(object):
6 #@trace
7 def __init__( self, someValue,name='Surendra' ):
8 """Create a MyClass instance."""
9 self.value= someValue
10 self.name=name
11 print "Name and Value ",self.name,self.value
12 #@trace
13 def doSomething( self, anotherValue,age=35):
14 """Update a value."""
15 self.value += anotherValue
16 print "another Value ",self.value
17
18
19 def hello1():
20 def hello(*arg):
21 '''This is hello document'''
22 print "Hi"
23 print "Hello1"
24 return hello()
25
26 hello1()
27
28 #mc=MyClass(23)
29 #mc.doSomething(60)
30 #m=MyClass(45)
31 #enter __init__
32 #exit __init__
33 #mc.doSomething( 15 )
34 #m.doSomething(54)
35 #enter doSomething
36 #exit doSomething
37 # print mc.value
38 #38
39
In [ ]:

1 # %load trace2.py
2 #trace.py
3 def trace(a):
4 """Trace entry, exit and exceptions."""
5 def hello(*b):
6 return b
7 return hello
8
9
10 print trace(78)
11
In [ ]:

1 # %load trace_client3.py
2 ##Here's a class which uses our @trace decorator.
3 ##trace_client.py
4 from trace1 import trace
5 class MyClass:
6 #@trace
7 ## def __init__( self, someValue):
8 ## """Create a MyClass instance."""
9 ## self.value= someValue
10 ## print "Value ",self.value
11 #@trace
12 ## def doSomething( self, anotherValue):
13 ## """Update a value."""
14 ## self.value += anotherValue
15 ## print "another Value ",self.value
16 @trace
17 def hello(self):
18 pass
19
20
21 mc=MyClass()
22 #mc.doSomething(60)
23 #m=MyClass(45)
24 #enter __init__
25 #exit __init__
26 #mc.doSomething( 15 )
27 #m.doSomething(54)
28 #enter doSomething
29 #exit doSomething
30 # print mc.value
31 #38
32
In [ ]:

1 # %load trace1.py
2 #trace.py
3 def trace( aFunc ):
4 """Trace entry, exit and exceptions."""
5 def loggedFunc(*args,**kwarg):
6 print "enter", aFunc.__name__
7 print "Document of Function", aFunc.__doc__
8 try:
9 result= aFunc(*args,**kwarg)
10 except Exception, e:
11 print "exception",aFunc.__name__, e
12 raise
13 print "exit", aFunc.__name__
14 #print result
15 return result
16 #loggedFunc.__name__= aFunc.__name__
17 #loggedFunc.__doc__= aFunc.__doc__
18 #print "Returning Log "
19 return loggedFunc
20
21
22 @trace
23 def add(x,y):
24 '''This is addition of Two Variable'''
25 return x+y
26
27
28 @trace
29 def sub(x,y):
30 ''' This is Substraction '''
31 return x-y
32
33
34 print add(5,8)
35 print sub(9,3)
36
37

1 Class Methods
2
3 Let’s compare that to the second method,
MyClass.classmethod. I marked this method with a
@classmethod decorator to flag it as a class method.
4
5 Instead of accepting a self parameter, class methods take a
cls parameter that points to the class—and not the object
instance—when the method is called.
6
7 Because the class method only has access to this cls
argument, it can’t modify object instance state. That would
require access to self. However, class methods can still
modify class state that applies across all instances of the
class.
8
9 Static Methods
10 The third method, MyClass.staticmethod was marked with a
@staticmethod decorator to flag it as a static method.
11
12 This type of method takes neither a self nor a cls
parameter (but of course it’s free to accept an arbitrary
number of other parameters).
13
14 Therefore a static method can neither modify object state
nor class state. Static methods are restricted in what data
they can access - and they’re primarily a way to namespace
your methods.
15
16 Let’s See Them In Action!
17 I know this discussion has been fairly theoretical up to
this point. And I believe it’s important that you develop
an intuitive understanding for how these method types
differ in practice. We’ll go over some concrete examples
now.
18
19 Let’s take a look at how these methods behave in action
when we call them. We’ll start by creating an instance of
the class and then calling the three different methods on
it.
20
21 MyClass was set up in such a way that each method’s
implementation returns a tuple containing information for
us to trace what’s going on — and which parts of the class
or object the method can access.
22
23 Here’s what happens when we call an instance method:
24
25 >>> obj = MyClass()
26 >>> obj.method()
27 ('instance method called', <MyClass instance at
0x101a2f4c8>)
28 This confirmed that method (the instance method) has access
to the object instance (printed as <MyClass instance>) via
the self argument.
29
30 When the method is called, Python replaces the self
argument with the instance object, obj. We could ignore the
syntactic sugar of the dot-call syntax (obj.method()) and
pass the instance object manually to get the same result:
31
32 >>> MyClass.method(obj)
33 ('instance method called', <MyClass instance at
0x101a2f4c8>)
34 Can you guess what would happen if you tried to call the
method without first creating an instance?
35
36 By the way, instance methods can also access the class
itself through the self.__class__ attribute. This makes
instance methods powerful in terms of access restrictions -
they can modify state on the object instance and on the
class itself.
37
38 Let’s try out the class method next:
39
40 >>> obj.classmethod()
41 ('class method called', <class MyClass at 0x101a2f4c8>)
42 Calling classmethod() showed us it doesn’t have access to
the <MyClass instance> object, but only to the <class
MyClass> object, representing the class itself (everything
in Python is an object, even classes themselves).
43
44 Notice how Python automatically passes the class as the
first argument to the function when we call
MyClass.classmethod(). Calling a method in Python through
the dot syntax triggers this behavior. The self parameter
on instance methods works the same way.
45
46 Please note that naming these parameters self and cls is
just a convention. You could just as easily name them
the_object and the_class and get the same result. All that
matters is that they’re positioned first in the parameter
list for the method.
47
48 Time to call the static method now:
49
50 >>> obj.staticmethod()
51 'static method called'
52 Did you see how we called staticmethod() on the object and
were able to do so successfully? Some developers are
surprised when they learn that it’s possible to call a
static method on an object instance.
53
54 Behind the scenes Python simply enforces the access
restrictions by not passing in the self or the cls argument
when a static method gets called using the dot syntax.
55
56 This confirms that static methods can neither access the
object instance state nor the class state. They work like
regular functions but belong to the class’s (and every
instance’s) namespace.
57
58 Now, let’s take a look at what happens when we attempt to
call these methods on the class itself - without creating
an object instance beforehand:
59
60 >>> MyClass.classmethod()
61 ('class method called', <class MyClass at 0x101a2f4c8>)
62
63 >>> MyClass.staticmethod()
64 'static method called'
65
66 >>> MyClass.method()
67 TypeError: unbound method method() must
68 be called with MyClass instance as first
69 argument (got nothing instead)

class method vs static method in


Python
Class Method

The @classmethod decorator, is a builtin function decorator that is an expression that


gets evaluated after your function is defined. The result of that evaluation shadows
your function definition. A class method receives the class as implicit first argument,
just like an instance method receives the instance Syntax:

class C(object): @classmethod def fun(cls, arg1, arg2, ...): .... fun: function that needs
to be converted into a class method returns: a class method for function. A class
method is a method which is bound to the class and not the object of the class. They
have the access to the state of the class as it takes a class parameter that points to
the class and not the object instance. It can modify a class state that would apply
across all the instances of the class. For example it can modify a class variable that
will be applicable to all the instances. Static Method

A static method does not receive an implicit first argument. Syntax:


class C(object): @staticmethod def fun(arg1, arg2, ...): ... returns: a static method for
function fun. A static method is also a method which is bound to the class and not the
object of the class. A static method can’t access or modify class state. It is present in
a class because it makes sense for the method to be present in class. Class method
vs Static Method

A class method takes cls as first parameter while a static method needs no specific
parameters. A class method can access or modify class state while a static method
can’t access or modify it. In general, static methods know nothing about class state.
They are utility type methods that take some parameters and work upon those
parameters. On the other hand class methods must have class as parameter. We use
@classmethod decorator in python to create a class method and we use
@staticmethod decorator to create a static method in python. When to use what?

We generally use class method to create factory methods. Factory methods return
class object ( similar to a constructor ) for different use cases. We generally use static
methods to create utility functions. How to define a class method and a static
method?

To define a class method in python, we use @classmethod decorator and to define a


static method we use @staticmethod decorator. Let us look at an example to
understand the difference between both of them. Let us say we want to create a class
Person. Now, python doesn’t support method overloading like C++ or Java so we use
class methods to create factory methods. In the below example we use a class
method to create a person object from birth year.

As explained above we use static methods to create utility functions. In the below
example we use a static method to check if a person is adult or not.

Implementation

filter_none edit play_arrow

brightness_4

Python program to demonstrate

use of class method and static


method.
from datetime import date
class Person: def init(self, name, age): self.name = name self.age = age

# a class method to create a Person object by birth year.


@classmethod
def fromBirthYear(cls, name, year):
return cls(name, date.today().year - year)

# a static method to check if a Person is adult or not.


@staticmethod
def isAdult(age):
return age > 18

person1 = Person('mayank', 21) person2 = Person.fromBirthYear('mayank', 1996)

print person1.age print person2.age

print the result


print Person.isAdult(22) Output

21 21 True This article is contributed by Mayank Agrawal. If you like GeeksforGeeks


and would like to contribute, you can also write an article using
contribute.geeksforgeeks.org or mail your article to [email protected]
(mailto:[email protected]). See your article appearing on the
GeeksforGeeks main page and help other Geeks.
In [30]:

1 def function1(*arg, **kwarg):


2 print(arg)
3 print(kwarg)
4
5
6 function1()
7 function1(5,7)
8 function1('hello','hi',23,34)
9 function1(2,3,4, var1=20, var2=40 )

()
{}
(5, 7)
{}
('hello', 'hi', 23, 34)
{}
(2, 3, 4)
{'var1': 20, 'var2': 40}

In [ ]:

You might also like