Many Pythonistas are familiar with using decorators, but far fewer understand what’s happening under the hood and can write their own. It takes a little effort to learn their subtleties but, once grasped, they’re a great tool for writing concise, elegant Python.

This post will briefly introduce the concept, start with a basic decorator implementation, then walk through a few more involved examples one by one.

What is a decorator

Decorators are most commonly used with the @decorator syntax. You may have seen Python that looks something like these examples.

@app.route("/home")
def home():
    return render_template("index.html")

@performance_analysis
def foo():
    pass

@property
def total_requests(self):
    return self._total_requests

To understand what a decorator does, we first have to take a step back and look at some of the things we can do with functions in Python.

def get_hello_function(punctuation):
    """Returns a hello world function, with or without punctuation."""

    def hello_world():
        print("hello world")

    def hello_world_punctuated():
        print("Hello, world!")

    if punctuation:
        return hello_world_punctuated
    else:
        return hello_world


if __name__ == '__main__':
    ready_to_call = get_hello_function(punctuation=True)

    ready_to_call()
    # "Hello, world!" is printed

In the above snippet, get_hello_function returns a function. The returned function gets assigned and then called. This flexibility in the way functions can be used and manipulated is key to the operation of decorators.

As well as returning functions, we can also pass functions as arguments. In the example below, we wrap a function, adding a delay before it’s called.

from time import sleep

def delayed_func(func):
    """Return a wrapper which delays `func` by 10 seconds."""
    def wrapper():
        print("Waiting for ten seconds...")
        sleep(10)
        # Call the function that was passed in
        func()

    return wrapper


def print_phrase(): 
    print("Fresh Hacks Every Day")


if __name__ == '__main__':
    delayed_print_function = delayed_func(print_phrase)
    delayed_print_function()

This can feel a bit confusing at first, but we’re just defining a new function wrapper, which sleeps before calling func. It’s important to note that we haven’t changed the behaviour of func itself, we’ve only returned a different function which calls func after a delay.

When the code above is run, the following output is produced:

$ python decorator_test.py
Waiting for ten seconds...
Fresh Hacks Every Day

Let’s make it pretty

If you rummage around the internet for information on decorators, the phrase you’ll see again and again is “syntactic sugar”. This does a good job of explaining what decorators are: simply a shortcut to save typing and improve readability.

The @decorator syntax makes it very easy to apply our wrapper to any function. We could re-write our delaying code above like this:

from time import sleep

def delayed_func(func):
...read more

Source:: Hackaday