Python
Topics:
Higher Order Functions and Decorators

Higher Order Functions and Decorators

  • Tutorial

Python Higher-Order Functions and Decorators

In Python, functions are treated as first class objects, allowing you to perform the following operations on functions.

  • A function can take one or more functions as arguments
  • A function can be returned as a result of another function

In this tutorial, you will learn:

1. Handling functions as arguments
2. Returning functions as the return value from other functions
3. Using closures and decorators

Functions as arguments

You can pass functions as one of the arguments to another function. This is shown in the following example.

def summation(nums): # normal function
    return sum(nums)

def main(f, *args): # function as an argument
    result = f(*args)
    print(result)

if __name__ == "__main__":
    main(summation, [1,2,3]) # output 6

The main function took in the function summation as an argument. The main function is a normal function which executes the supplied function with the arguments. You can see that the output reflects that. This opens up possibilities where you can pass different functions to a function, and the passed function only will be considered.

Having a function as a return value

def add_tw0_nums(x, y): # normal function which returns data
    return x + y

def add_three_nums(x, y, z): # normal function which returns data
    return x + y + z

def get_appropriate_function(num_len): # function which returns functions depending on the logic
    if num_len == 3:
        return add_three_nums
    else:
        return add_tw0_nums


if __name__ == "__main__":
    args = [1, 2, 3]
    num_len = len(args)
    res_function = get_appropriate_function(num_len)
    print(res_function)       # <function add_three_nums at 0x7f8f34173668>
    print(res_function(*args)) # unpack the args, output 6

    args = [1, 2]
    num_len = len(args)
    res_function = get_appropriate_function(num_len)
    print(res_function)       # <function add_tw0_nums at 0x7f1630955e18>
    print(res_function(*args)) # unpack the args, output 3

Here, you can see that different functions were returned depending on the argument in the “get_appropriate_function.”

Python closures

Let’s now look at how closures are created in Python. A closure is a way of keeping alive a variable even when the function has returned. So, in a closure, a function is defined along with the environment. In Python, this is done by nesting a function inside the encapsulating function and then returning the underlying function.

def add_5():
    five = 5

    def add(arg): # nesting functions
        return arg + five
    return add

if __name__ == '__main__':
    closure1 = add_5()
    print(closure1(1)) # output 6
    print(closure1(2)) # output 7

Python decorators

Now, using the these ways of writing, Python code can be used to create decorators. Python decorators are convenient ways to make changes to the functionality of code without making changes to the code. A decorator is written as a function closure and implemented by giving the “@” operator on top of the function. The skeleton of a Python decorator is shown below.

def my_decorator(f):
    # write your code here or your wrapping function
    # return the wrapping function
    pass

@my_decorator
def my_code(args):
    # original functionality
    pass

my_code(args)

Using this skeleton as an example, giving the @ operator on top of the function is the same as writing my_decorator(my_code(args)).

You can look at the following example where you have a function that returns a dictionary.

def my_code(args):
    return {lang: args}

Executing this code will give the normal result.

>>> def my_code(args):
...     return {"lang": args}
...
>>> print(my_code("python"))
{'lang': 'python'}
>>>

Say, you want to build the additional functionality of checking if the return value is a dict. In this case, you can use a decorator.

>>> def check(f):
...     def wrapper(*args, **kwargs):
...         res = f(*args, **kwargs)
...         if isinstance(res, dict):
...             print("checked that the return value is dict")
...             return res
...     return wrapper
...
>>>
>>>
>>> @check
... def my_code(args):
...     return {"lang": args}
...
>>> print(my_code("python"))
checked that the return value is dict
{'lang': 'python'}

Here, the function “check” is used as a decorator, and it enforces the additional functionality of checking if the return of the function ”my_code” is a decorator or not.

Contributed by: Joydeep Bhattacharjee
Notifications
View All Notifications