{ Decorators. }

Objectives

By the end of this chapter, you should be able to:

  • Understand what a decorator is
  • Create your own decorators to add additional functionality to functions

Decorators

Decorators are functions that "decorate," or enhance, other functions. In order to see what this means, let's first review how we can pass functions to other functions. Remember, everything in Python is an object and objects are first class!

def shout():
    return "WHOA!"

def whisper():
    return "Shhhh"

def perform_action(func):
    print("something is happening")
    return func()

perform_action(shout)
# something is happening
# 'WHOA!'

perform_action(whisper)
# something is happening
# 'Shhhh'

We can write the behavior of a decorator like this:

def new_decorator(func):
    def wrap_func():
        print("code before func!")
        func()
        print("code after func!")
    return wrap_func

def decorate_me():
    print("decorate me!")

decorate_me = new_decorator(decorate_me)

decorate_me() # What do you think this will print?

Let's now use the decorator syntax to do the same thing! When you use a decorator, the function being used to decorate is prefixed with an @ symbol. The function you're decorating is then defined below. Take a look:

def new_decorator(func):
    def wrap_func():
        print("code before func!")
        func()
        print("code after func!")
    return wrap_func

@new_decorator
def decorate_me():
    print("decorate me!")

Note how the code inside of the new_decorator function decorates, or enhances, the code inside of the decorate_me function.

Let's revisit the first example but refactor it to use decorator syntax.

def perform_action(func):
    def wrap_func():
        print("something is happening")
        return func()
    return wrap_func

@perform_action
def whisper():
    return "Shhhh"

@perform_action
def shout():
    return "WHOA!"

whisper()
# something is happening
# 'Shhhh'

shout()
# something is happening
# 'WHOA!'

This code will work just fine, but if we examine the __name__ or __doc__ attribute for our function it will not be correct!

shout.__name__ # 'wrap_func' - oops!

We can manually fix this, or we can use the wraps decorator from the functools module.

from functools import wraps 

def perform_action(func):
    @wraps(func)
    def wrap_func():
        print("something is happening")
        return func()
    return wrap_func

@perform_action
def whisper():
    return "Shhhh"

@perform_action
def shout():
    return "WHOA!"

shout.__name__ # 'shout' - much better   

When you're ready, move on to Lambdas and Dates

Continue