- Python Decorator with Arguments Python Decorator Tutorial with Example
- Function Decorator
- Method Decorator
- Class Decorators
- Chaining Decorators
- Functools and Wraps
- Decorators with Arguments
- Class Based Decorators with Arguments
- Python Decorator with Arguments
- Introduction to Python decorator with arguments
- Summary
Python Decorator with Arguments Python Decorator Tutorial with Example
If you want to take a really deep dive, you should read these exhaustive articles by Graham Dumpleton. However, if you intend to get started and getting better at reading/writing python decorators, this article should suffice. Everything is an object in python, even functions. A function can be assigned to a variable, passed to another function and can be returned from another function. Take a look at the example below:
[1]: def outer_function(): . print "1. This is outer function!" . def inner_function(): . print "2. This is inner function, inside outer function!" . print "3. This is outside inner function, inside outer function!" . return inner_function() . [2]: func_assign = outer_function() 1. This is outer function! 3. This is outside inner function, inside outer function! 2. This is inner function, inside outer function!
Mark in the above execution, how the statement inside the inner function is printed at the last, consequential to inner_function being returned, at the end of outer_function, and the execution could be seen during the assignment. Python decorator are the function that receive a function as an argument and return another function as return value. The assumption for a decorator is that we will pass a function as argument and the signature of the inner function in the decorator must match the function to decorate.
Function Decorator
Let us now, write a simple function decorator for ourselves. We will write a decorator that would measure the execution time of the function passed to it.
import time def timetest(input_func): def timed(*args, **kwargs): start_time = time.time() result = input_func(*args, **kwargs) end_time = time.time() print "Method Name - , Args - , Kwargs - , Execution Time - ".format( input_func.__name__, args, kwargs, end_time - start_time ) return result return timed @timetest def foobar(*args, **kwargs): time.sleep(0.3) print "inside foobar" print args, kwargs foobar(["hello, world"], foo=2, bar=5) inside foobar (['hello, world'],) 'foo': 2, 'bar': 5> Method Name - foobar, Args - (['hello, world'],), Kwargs - 'foo': 2, 'bar': 5>, Execution Time - 0.30296087265
We passed the function foobar to decorator named timetest. Inside decorator, function foobar is referenced as variable input_func. The result, post execution of input_func is referred as result. Prepending @ to the name of the decorator, and writing the same above a function calls the decorator, and passes the function to the decorator(decorates).
Method Decorator
Method decorators allow overriding class properties by decorating, without having to find the calling function.
def method_decorator(method): def inner(city_instance): if city_instance.name == "SFO": print "Its a cool place to live in." else: method(city_instance) return inner class City(object): def __init__(self, name): self.name = name @method_decorator def print_test(self): print self.name p1 = City("SFO") p1.print_test() Its a cool place to live in.
In the snippet shown above, we decorate the class method print_test. The method_decorator prints the name of the city, if the name of city instance is not SFO.
Class Decorators
If you want to create a callable returning another callable, the function decorator approach is easier. If you want the return to be a function, function decorators should be preferred, however if you want the decorator to return a custom object that does something different to what a function does, in that case a class decorator should be used. With a class, you can add methods and properties to the decorated callable object, or implement operations on them. You can create descriptors that act in a special way when placed in classes (e.g. classmethod, property)
class decoclass(object): def __init__(self, f): self.f = f def __call__(self, *args, **kwargs): # before f actions print 'decorator initialised' self.f(*args, **kwargs) print 'decorator terminated' # after f actions @decoclass def klass(): print 'class' klass()
Chaining Decorators
The chaining of decorator is similar to how multiple inheritance can be used to construct classes We can write as many decorator as we want and include them one by one in decoration line with decoration syntax before the definition of function to be decorated.
def makebold(f): return lambda: "" + f() + "" def makeitalic(f): return lambda: "" + f() + "" @makebold @makeitalic def say(): return "Hello" print say()
One thing should be kept in mind that the order of decorators we set matters. When you chain decorators, the order in which they are stacked is bottom to top.
Functools and Wraps
def decorator(func): """decorator docstring""" def inner_function(*args, **kwargs): """inner function docstring """ print func.__name__ + "was called" return func(*args, **kwargs) return inner_function @decorator def foobar(x): """foobar docstring""" return x**2
print foobar.__name__ print foobar.__doc__ inner_function inner function docstring
The above observation leads us to conclude that the function foobar is being replaced by inner_function. This means that we are losing information about the function which is being passed. functools.wraps comes to our rescue. It takes the function used in the decorator and adds the functionality of copying over the function name, docstring, arguemnets etc. Lets decorate without losing information:
from functools import wraps def wrapped_decorator(func): """wrapped decorator docstring""" @wraps(func) def inner_function(*args, **kwargs): """inner function docstring """ print func.__name__ + "was called" return func(*args, **kwargs) return inner_function @wrapped_decorator def foobar(x): """foobar docstring""" return x**2 print foobar.__name__ print foobar.__doc__ foobar foobar docstring
The above implementation preserves the information about the funciton being passed to the decorator. How would you go about caching information inside a class based decorator? One of the ways of doing it, is listed here , would love to see more implementation, in comments.
Decorators with Arguments
from functools import wraps def decorator(arg1, arg2): def inner_function(function): @wraps(function) def wrapper(*args, **kwargs): print "Arguements passed to decorator %s and %s" % (arg1, arg2) function(*args, **kwargs) return wrapper return inner_function @decorator("arg1", "arg2") def print_args(*args): for arg in args: print arg print print_args(1, 2, 3) Arguements passed to decorator arg1 and arg2 1 2 3
Class Based Decorators with Arguments
class ClassDecorator(object): def __init__(self, arg1, arg2): print "Arguements passed to decorator %s and %s" % (arg1, arg2) self.arg1 = arg1 self.arg2 = arg2 def __call__(self, foo, *args, **kwargs): def inner_func(*args, **kwargs): print "Args passed inside decorated function .%s and %s" % (self.arg1, self.arg2) return foo(*args, **kwargs) return inner_func @ClassDecorator("arg1", "arg2") def print_args(*args): for arg in args: print arg print_args(1, 2, 3) Arguements passed to decorator arg1 and arg2 Args passed inside decorated function .arg1 and arg2 1 2 3
How would you go about implementing decorator with optional arguments? Try following this SO Post. You might want to further explore the Wrapt Library. The article originally appeared on Apcelent Tech Blog.
Python Decorator with Arguments
Summary: in this tutorial, you’ll learn how to define Python decorators with arguments using a decorator factory.
Introduction to Python decorator with arguments
Suppose that you have a function called say that prints out a message:
def say(message): ''' print the message Arguments message: the message to show ''' print(message)
Code language: Python (python)
and you want to execute the say() function 5 times repeatedly each time you call it. For example:
say('Hi')
Code language: Python (python)
It should show the following the Hi message five times as follows:
To do that, you can use a regular decorator:
@repeat def say(message): ''' print the message Arguments message: the message to show ''' print(message)
Code language: Python (python)
And you can define the repeat decorator as follows:
def repeat(fn): @wraps(fn) def wrapper(*args, **kwargs): for _ in range(5): result = fn(*args, **kwargs) return result return wrapper
Code language: Python (python)
The following shows the complete code:
from functools import wraps def repeat(fn): @wraps(fn) def wrapper(*args, **kwargs): for _ in range(5): result = fn(*args, **kwargs) return result return wrapper @repeat def say(message): ''' print the message Arguments message: the message to show ''' print(message) say('Hello')
Code language: Python (python)
What if you want to execute the say() function repeatedly ten times. In this case, you need to change the hard-coded value 5 in the repeat decorator.
However, this solution isn’t flexible. For example, you want to use the repeat decorator to execute a function 5 times and another 10 times. The repeat decorator would not meet the requirement.
To fix this, you need to change the repeat decorator so that it accepts an argument that specifies the number of times a function should execute like this:
@repeat(5) def say(message): .
Code language: Python (python)
To define the repeat decorator, the repeat(5) should return the original decorator.
def repeat(times): # return the original "repeat" decorator
Code language: Python (python)
The new repeat function returns a decorator. And it’s often referred to as a decorator factory.
The following repeat function returns a decorator:
def repeat(times): ''' call a function a number of times ''' def decorate(fn): @wraps(fn) def wrapper(*args, **kwargs): for _ in range(times): result = fn(*args, **kwargs) return result return wrapper return decorate
Code language: Python (python)
In this code, the decorate function is a decorator. It’s equivalent to the original repeat decorator.
Note that the new repeat function isn’t a decorator. It’s a decorator factory that returns a decorator.
from functools import wraps def repeat(times): ''' call a function a number of times ''' def decorate(fn): @wraps(fn) def wrapper(*args, **kwargs): for _ in range(times): result = fn(*args, **kwargs) return result return wrapper return decorate @repeat(10) def say(message): ''' print the message Arguments message: the message to show ''' print(message) say('Hello')
Code language: Python (python)