Python Decorators

We know python functions are objects:

Hence, a function can return another function.

Decorators are wrappers, they let you execute code before and after a function without the need to modify the function itself.

1 def decorator(fn):
2     def wrapper():
3         print “Before fn runs”
4         fn()
5         print “After fn runs”
6     return wrapper
 

Decorator syntax:

 1 @decorator
 2 def standalone_fn():
 3     print “this is a stand alone fn”
 4 
 5 standalone_fn()
 6 
 7 # prints:
 8 # Before fn runs
 9 # this is a stand alone fn
10 # After fn runs

This is equivalent to manually doing:

1 # this is equivalent to:
2 standalone_fn = decorator(standalone_fn)
3 
4 standalone_fn()
5 
6 # prints:
7 # Before fn runs
8 # this is a stand alone fn
9 # After fn runs

Say, my standalone function had arguments, how do we pass arguments to decorators? Let the wrapper pass the arguments.

1 def decorator(fn):
2     def wrapper(*args, **kwargs):
3         print “Before fn runs”
4         fn(*args, **kwargs)
5         print “After fn runs”
6     return wrapper

Passing arguments to decorators:

What if you want decorators to have arguments themselves. You need to wrap the decorator with a function which accepts those arguments and returns your decorator.

 1 def decorator_maker(arg1, arg2):
 2     def decorator(f):
 3         def wrapper(*args, **kwargs):
 4             print arg1
 5             f(*args, **kwargs)
 6             print arg2
 7         return wrapper
 8     return decorator
 9 
10 # this can be used as:
11 
12 @decorator_maker(‘before’, ‘after’)
13 def fn_to_decorate(fn_a1, fn_a2):
14     # do something here like:
15     print fn_a1, fn_a2

Let’s take an example now, I used this for returning JSON in addition to HTML from flask view functions that return dicts.

 1 def render(template=None):
 2     def decorator(f):
 3         @wraps(f)
 4         def decorated_function(*args, **kwargs):
 5             ctx = f(*args, **kwargs)
 6             if ctx is None:
 7                 ctx = {}
 8             elif not isinstance(ctx, dict):
 9                 return ctx
10             render_type = request_wants_json()
11             if render_type:
12                 return jsonify(ctx)
13             return render_template(template, **ctx)
14         return decorated_function
15     return decorator

What this decorator does is, it takes the dict returned by view (the function f) in ctx,  if the requested wanted JSON, returns the jsonified dict, else renders as HTML using the template. The decorator itself requires an argument for template.

What is @wraps(f) ?

When you use a decorator, you’re replacing one function with another. That means if you print f.name it will decorated_function instead of f (if @wraps(f) is not used). Quoting from docs

This is a convenience function for invoking partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated) as a function decorator when defining a wrapper function.

Bibliography: