Python: Decorator Tutorial

,

What is Decorator?

Python “decorator” is a language construct that lets you define a wrapper function g to another function f, such that, when f is called, your wrapper g is called instead. (it can also be applied to methods, and class.)

The syntax for “decorator” is @name1 immediately before the line of def name2.

Here's a decorator:

@g
def f():
    print("xyz")

is equivalent to

def f():
    print("xyz")
f = g(f)

That is ALL there is of Python decorators.

Note that:

  1. when the function f is called, the wrapper g will be called instead.
  2. the wrapper function g receives f as its argument.
  3. the wrapper function g must return a function. Because, remember, the value of f is now g(f), and f is a function. So, when f is called such as f(3), Python evaluates g(f)(3), so g(f) must be a function too. (technically, it just need to be anything callable.)

Here's a example of decorator:

# -*- coding: utf-8 -*-
# python

# example of a decorator

def gg(xx):
    print "gg called"
    def s(y):
        return y-1
    return s

@gg  # ← this is decorator
def ff(x):
    print "ff called"
    return x+1

print ff(3)

# output:
# gg called
# 2

Note: the wrapper function gg receives the function ff. The wrapper can do anything with it. Typically, it'll eventually call ff, but it doesn't have to.

In the above example, the wrapper gg bypass ff entirely.

Checklist for Writing a Decorator

Here's 4 things to remember when writing a decorator:

  1. The wrapper function g must take a function as argument. (because it'll receive f as argument.)
  2. The wrapper function g must return a function. (because that'll be applied to f's arguments.)
  3. The returned function h must take the same number/type of arguments as f.
  4. OFTEN, the returned function h should return the same type of value that f returns. If f returns a string, h probably should too. If f returns None, h probably should too.

Decorator Examples

Decorator Example: Check/Modify Input Argument

Here's a example of decorator. It (in effect) modifies the function's argument.

# -*- coding: utf-8 -*-
# python

# example of a decorator

def gg(func):
    """a decorator for ff. Make sure input to ff is always even. If not, add 1 to make it even"""
    def hh(y):
        if y % 2 == 0:          # even
            return func(y)
        else:
            return func(y+1)
    return hh

@gg
def ff(x):
    return x

print ff(3)                     # 4
print ff(4)                     # 4
print ff(5)                     # 6
print ff(6)                     # 6

Note: decorator function gg does not know what the decorated function ff's arguments. However, since gg is a wrapper, it can create and return a new function hh, and have hh check arguments then call ff(args). This works because whatever arguments passed to the original function ff is passed to hh, then hh can do arg checking, then call ff.

Decorator Example: Catch All Arguments (And Run Conditionally)

When defining the decorator function, often you need to catch all possible arguments of the original function. Here's a example of how.

In the following example, the original function is called only if some global variable is true.

# -*- coding: utf-8 -*-
# python

# example of decorator that check condition to decide whether to call

cc = True                       # ← the condition

def gg(func):
    """a decorator for ff. If cc is true, run ff, else do nothing"""
    def hh(*args, **kwargs): # ← param must catch all args of ff
        """do nothing function"""
        pass
    if cc:
        return func
    else:
        return hh

@gg
def ff(*args, **kwargs):
    # ff can be sending email, or any callable with no return value
    print "ff"
    pass

ff(3)
ff(3,"Jane")
ff(3,4, k="thank you")

# if cc is true, then ff is called 3 times
# (different set of arguments are used, to illustrate that our wrapper work well with them)

# if cc is false, Nothing's done, ff is not called.

〔➤ Python: Function with Optional Parameter, Named Parameter, Infinite Parameters

Decorator with Parameters

Decorator itself can have parameters.

For example, the following decorator:

@g(3)
def f():
    print("xyz")

is equivalent to

def f():
    print("xyz")
f = g(3)(f)

There's nothing special about this. In the simplest decorator example, @g followed by def f: gets transformed into f = g(f). Now, if we replace g by g(x), we get f = g(x)(f).

Decorators can get complex, because it's transforming code and doing function applications and involves nested functions. In this case, just remember that:

Here's a example:

# -*- coding: utf-8 -*-
# python

# example of decorator with argument

def gg(num):
    """a decorator for ff. Ignore ff. Simply return num."""
    def h1(f1):
        def h2(f2):
            return num
        return h2
    return h1

# gg(1) must return a function h1, such that h1 accept function (the ff), and also return a function (to be applied to ff's args)
@gg(1)
def ff(x):
    return repr(x) + " rabbits"

print ff(3)                     # 1
print ff(4)                     # 1
print ff(5)                     # 1

References

blog comments powered by Disqus