Python: Decorator

By Xah Lee. Date: . Last updated: .

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:

# define function g here

@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:

Here's a example of decorator:

# example of a decorator

def gg(xx):
    print("gg called")

    def hh(y):
        return y - 1

    return hh

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

print(ff(3))

# prints
# gg called

# return
# 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.

Why Use Decorator

typically, decorators are use to add class methods or static methods, or setting pre-conditions, synchronisation.

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 (e.g. h). (because calling f results in calling return value of g.)
  3. The returned function h must take the same number and type of arguments as f. (Tip: use def h(*args, **keywords) to catch all.)
  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 or Modify Argument

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

# 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.

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

# the condition
cc = True

def gg(func):
    """a decorator for ff. If cc is true, run ff, else do nothing"""

    def hh(*args, **xkeywords):  # param must catch all args of ff
        """do nothing function"""
        pass

    if cc:
        return func
    else:
        return hh

@gg
def ff(*args, **xkeywords):
    # 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.

〔see Python: Function

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:

Example:

# 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

Example: Decorator as a Class

# example of decorator on class


class Dog:

    def __init__(self, hh):
        print("Dog.__init__() called")
        hh()  # Prove that function definition has completed

    def __call__(self):
        print("Dog.__call__() called")

@Dog  # this forces Dog to be called when ff is called
def ff():
    print("ff() called")

ff()
# Dog.__init__() called
# ff() called
# Dog.__call__() called

Example 2

class Gate(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print("enter", self.f.__name__)
        self.f()
        print("exit", self.f.__name__)

@Gate
def f1():
    print("f1() called")

@Gate
def f2():
    print("f2() called")

f1()
f2()

# enter f1
# f1() called
# exit f1
# enter f2
# f2() called
# exit f2

Reference

Python, Function and Class