Python: Decorator Tutorial

By Xah Lee. Date:

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 2

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

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

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

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

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

# 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

If you have a question, put $5 at patreon and message me.

Python

  1. Python 3 Basics
  2. Python 2 Basics
  3. Python 2 and 3 Difference
  4. Print Version
  5. Builtin Help
  6. Quote String
  7. String Methods
  8. Format String
  9. Operators
  10. Complex Numbers
  11. True, False
  12. if then else
  13. Loop
  14. List Basics
  15. Loop Thru List
  16. Map f to List
  17. Copy Nested List
  18. List Comprehension
  19. List Methods
  20. Sort
  21. Dictionary
  22. Loop Thru Dict
  23. Dict Methods
  24. Tuple
  25. Sets
  26. Function
  27. Closure
  28. 2 Closure
  29. Decorator
  30. Class
  31. Object, ID, Type
  32. List Modules
  33. Write a Module
  34. Unicode 🐍

Regex

  1. Regex Basics
  2. Regex Reference

Text Processing

  1. Read/Write File
  2. Traverse Directory
  3. File Path
  4. Process Unicode
  5. Convert File Encoding
  6. Find Replace in dir
  7. Find Replace by Regex
  8. Count Word Frequency

Web

  1. Send Email
  2. GET Web Page
  3. Web Crawler
  4. HTTP POST

Misc

  1. JSON
  2. Find Script Path
  3. Get Env Var
  4. System Call
  5. Decompress Gzip
  6. Append String in Loop
  7. Timing f timeit
  8. Keyword Arg Default Value Unstable
  9. Check Page Load Size
  10. Thumbnail Generation