Everyone who has used python , has sooner or later come across decorators . That's that thing sitting on a function which magically does something special . It looks like :
A decorator is a function that takes another function as input, wraps it with additional behavior, and returns the modified function — without changing the original function's code.
@mydecorator
def my_function
Today we are going to learn , how to write decorators of our own . How this works:
A decorator is a function that takes another function as input, wraps it with additional behavior, and returns the modified function — without changing the original function's code.
#Example: A decorator for function with no arguments
def square(func):
def inner_func():
return func() ** 2
return inner_func
@square
def number():
return 5
Designing Steps of the above example:
1. We define a decorator function like a normal function. Example : square function (Refer above example).2. It takes one argument . That argument will be a function.
3. Inside the function , we define another function (name doesn't matter) and return that function argument itself (or with some processing like above) .
4. Finally , we return the inner function but, without parenthesis. So it doesn't execute.
Now when this function that we defined is applied to another, it of course gives you a square of a number. But what's happening behind the scene is.
1. An object of the inner_function type gets created .
f = square(number)
2. f
<function inner_func at 0x00000000032892E8>
3. f ()
25
Now what if we wanted to apply the decorator on a function that accepts an argument . Like our square function above.
#Example with function that takes arguments (In this case, a string) def reverse_string(func): def inner_func(*args, **kwargs): text1, text2 = func(*args, **kwargs) print 'Reversed String:' + text1[::-1] + text2[::-1] return inner_func
@reverse_string def get_string(s, s1): return s, s1
>>> get_string('Let"s' , 'decorate')
Reversed String:s"teLetaroced
Decorator with arguments
What if we want to pass arguments with the decorator itself. Let's see an example.
def multiplyby(num): def middlefun(func): def inner_fun(*args, **kwargs): value = func(*args) return value * num return inner_fun return middlefun @multiplyby(10) def mynum(num): return num print mynum(5)
>>> 50
Let's build a cool time measuring decorator that can measure execution time of any function.
# A decorator to measure function time def measure_this(func): import time def inside_func(*args, **kwargs): t1 = time.time() my_func = func(*args, **kwargs) t2 = time.time() print 'It took {} seconds to run the function: {}'.format(t2 - t1, func.__name__) return inside_func @measure_this def lets_wait(t): import time time.sleep(t) return #Example usage: >>> lets_wait(1) It took 1.0 seconds to run the function: lets_wait >>> lets_wait(3) It took 3.0 seconds to run the function: lets_wait
We can design a decorator using classes too.
But before we get into that , I want to tell you something about a special method called
__call__
According to Python docs, Class instances are callable only when the class has a
__call__() method . See the example below.>>> class Hello: def __init__(self, name): self.name = name def __call__(self): return 'Calling %s' % self.name >>> h = Hello('Arc') >>> h <__main__.Hello instance at 0x0000000003159508> >>> h() 'Calling Arc'
If we don't define that __call__ method, and we try to run the class instance, we get an error
>>> h()
Traceback (most recent call last):
File "<pyshell#26>", line 1, in <module>
h()
AttributeError: Hello instance has no __call__ method
With this knowledge , we go ahead and design a class decorator
# Class based decorator class decorator_class(object): def __init__(self, func): self.original_func = func def __call__(self, *args, **kwargs): return 'Hi ' + self.original_func(*args, **kwargs) @decorator_class def hello(name): return name >>> hello('Batman') 'Hi Batman'
No comments:
Post a Comment