Decorators and Functions in Python — Handy Python Features (Part 1)

__how_can_we_use_decorators__.py

Python is a powerful language and there are many handy built-in features. Decorators are one of them. You might have seen decorators in Python code as in the following example:

Decorators in use: @staticmethod is a decorator

Decorators are functions that wrap other functions and change their behavior. Python describes decorators with PEP-0318. Before discussing what decorators are and how they can be used, we need to understand what are functions in Python and the way Python handles them.

Functions

Simply, a function can take arguments and do a job with them. For example, the print function in the above example takes a string input as an argument and prints it to the console. There are also functions that do not accept any arguments. Some functions return output and some do not.

Inner Functions

Python defines a function when it sees the keyword def . Functions are also allowed to be defined inside a function as shown in the code below. Those are called inner functions.

Inner functions : two inner function definitions inside print_greeting() function

The function print_greeting() has two inner functions. One of them is invoked based on the argument uppercase when it is called.

First-Class Objects

Functions in python are treated as first-class objects. Therefore functions can be passed as arguments to another function just like strings, integers, or any other object. The following example shows how to pass a function into a function.

Functions are first-class objects : print_hi() is passed as an argument

The function run_any_function(function) takes any function as an argument and invokes it. Here it takes the function print_hi() .

Functions can also be returned from a function as the output:

Functions are first-class objects : get_capable_function() returns a function

The function get_capable_function(uppercase) returns a function based on the argument uppercase . If the argument is true, the function print_text_uppercase(text) is returned and the function print_text(text) is returned in any other case.

Now we have the background to discuss how decorators work.

Decorators

Decorators are functions that wrap a function and change their behavior. These functions take a function as an argument. They have an inner function defined which can invoke the function taken as the argument changing its behavior. The inner function is returned as the output:

Decorators : print_hi_and_name(name_only_function) is a decorator

The above example shows a basic decorator. The function print_hi_and_name(name_only_function) takes the function print_name() as an argument. It is called by the inner function add_hi() which changes the behavior by printing “Hi”. The inner function is returned hence the function print_name can be re-declared with the decorator function.

@ Syntax

There is a more concise and short way to achieve the same result instead of re-declaring the function as we did in line 12 in the above example.

Decorators : print_name() is decorated with @print_hi_and_name

Functions with arguments

The function print_name() prints a hard-coded value. Suppose we need to pass a name as an argument into that function and the new function becomes print_name(name) now. But the inner function which invokes this does not know the arguments required by the function. It should also be changed accordingly which would give an error otherwise:

Decorators : print_name(name) is decorated with @print_hi_and_name

The inner function add_hi() in the above example is modified as add_hi(name) which changes it to be able to take the argument name which could then be passed to the function print_name(name) .

We also can change the function print_hi_and_name(name_only_function) to print_greeting_and_name(name_only_function, greeting) which can then take an extra argument greeting as follows:

Decorators : print_name(name) is decorated with @print_greeting_and_name(greeting)

Arguments as *args and **kwargs

Suppose we needed to modify the function print_name(name) to print a salutation before the name. The modified function would then take a new argument salutation .

Previously we had to modify the inner function to take the arguments required by the decorated function. The same thing is required for the new argument as well. This is not accepted and not a good practice. The argument that should be passed to the decorated function should not be known by the decorator.

We can use a trick for that. The trick is *args and **kwargs . The function add_greeting(name) can be changed to add_greeting(*args,**kwargs) and then the function name_only_function(name) can be called as name_only_function(*args,**kwargs) as follows. We can now change the function print_name(name) as print_name(salutation, name) :

Decorators : print_name(salutation, name) is decorated with @print_greeting_and_name(greeting)

An application

Languages like Java have access modifiers that are used to control the scope of a method. Methods can be made private by using the keyword private . But Python has no such concept.

We can write a decorator to make private functions in a class unable to be accessed outside. We just need to check whether the private function is called from outside or inside the class.

Decorators: @private_function is a custom decorator

The decorator can be used for isolating private functions as follows:

Decorators: @private_function is used to prevent private methods being called from outside

The decorated functions with @private_function are only allowed to be called inside the class. An exception is raised if those are called from the outside.

Decorators: errors are raised when calling private functions

You can find the Git repository in the following location.

Further reading…

Decorators are handy. They can be used with classes as well. The singleton pattern can be implemented using decorators. I would suggest you read more on that.