Decorators

Functions which modify the functionality of other functions.

Everything in Python is an object

First of all let’s understand functions in Python:

def hi(name="yasoob"):
    return "hi " + name

print(hi())
# output: 'hi yasoob'

# We can even assign a function to a variable like
greet = hi
# We are not using parentheses here because we are not calling the function hi
# instead we are just putting it into the greet variable. Let's try to run this

print(greet())
# output: 'hi yasoob'

# Let's see what happens if we delete the old hi function!
del hi
print(hi())
#outputs: NameError

print(greet())
#outputs: 'hi yasoob'

Defining functions within functions

In Python we can define functions inside other functions, ie. nested functions:

Returning functions from within functions

It is not necessary to execute a function within another function, we can return it as an output as well:

When you put a pair of parentheses after it, the function gets executed; whereas if you don’t put parenthesis after it, then it can be passed around and can be assigned to other variables without executing it.

Giving a function as an argument to another function

Decorators let you execute code before and after a function.

Writing our first decorator

Decorators wrap a function and modify its behavior in one way or the another.

The @ syntax is just a short way of making up a decorated function. Here is how we could have run the previous code sample using @.

Now there is one problem with our code. If we run:

That’s not what we expected! Its name is “a_function_requiring_decoration”. Well our function was replaced by wrapTheFunction. It overrode the name and docstring of our function. Luckily Python provides us a simple function to solve this problem and that is functools.wraps. Let’s modify our previous example to use functools.wraps:

Blueprint:

Note: @wraps takes a function to be decorated and adds the functionality of copying over the function name, docstring, arguments list, etc. This allows to access the pre-decorated function’s properties in the decorator.

Use cases

Authorization

Check whether someone is authorized to use an endpoint in a web applicatio

Example:

Logging

Example:

Decorators with Arguments

@wraps is also a decorator. But, it takes an argument like any normal function can do.

This is because when you use the @my_decorator syntax, you are applying a wrapper function with a single function as a parameter. Remember, everything in Python is an object, and this includes functions! We can write a function that returns a wrapper function.

Nesting a Decorator Within a Function

Create a wrapper which lets us specify a logfile to output to:

Decorator Classes

Now we have our logit decorator in production, but when some parts of our application are considered critical, failure might be something that needs more immediate attention. Let’s say sometimes you want to just log to a file. Other times you want an email sent, so the problem is brought to your attention, and still keep a log for your own records. This is a case for using inheritance, but so far we’ve only seen functions being used to build decorators.

Classes can also be used to build decorators.

Rebuild logit as a class instead of a function.

This implementation has an additional advantage of being much cleaner than the nested function approach, and wrapping a function still will use the same syntax as before:

Now, let’s subclass logit to add email functionality:

From here, @email_logit works just like @logit but sends an email to the admin in addition to logging.

Last updated