A Dive Into Python Closures and Decorators - Part 1

This is a multi-part guest tutorial post by Moyosore Sosan, an Andela developer based in Lagos.We will be looking at local functions, the concepts of closure and what decorators are, sounds like a lot? Then let's dive into it.Local functionsProbably most functions you are familiar with are either defined in module/global scope or within classes i.e methods. However Python allows us to define our functions in a local scope i.e within a function.Knowing this, local functions are functions defined within another function. We say these functions are defined to the scope of a specific function e.g[pastacode lang="python" manual="def%20remove_first_item(my_array)%3A%0A%20%20def%20get_first_item(s)%3A%0A%20%20%20%20return%20s%5B0%5D%0A%20%20my_array.remove(get_first_item(my_array))%0A%20%20return%20my_array%0A%20%20%20%20%0Aprint(remove_first_item(%5B'1'%2C'2'%2C'3'%5D)))%09%23%20%5B'2'%2C'3'%5D%0Aprint(get_first_item(%5B1%2C2%2C3%2C4%5D))%0A%23%20NameError%3A%20name%20'get_first_item'%20is%20not%20defined" message="" highlight="" provider="manual"/]From the above, we see that calling get_first_item throws an error. This is because it can only be accessed within the remove_first_item function, that makes it a local function.Local functions can also be returned from functions. Returning a local function is similar to returning in any other object. Let's take a look:[pastacode lang="python" manual="def%20enclosing_func()%3A%0A%20%20def%20local_func()%3A%0A%20%20%20%20print%20('I%20am%20a%20local%20function')%0A%20%20return%20local_func()%0A%20%0A%20print(enclosing_func())%09%23%20I%20am%20a%20local%20function'" message="" highlight="" provider="manual"/]Local functions are subject to the same scoping rules as other functions, this brings to the LEGB rule for name look up in python - checking starts with the local scope, then the enclosing, then global and finally the built-in scopeLEGB - Local, Eclosing, Global, Built-in[pastacode lang="python" manual="x%20%3D%20'global'%0Adef%20outer_func()%3A%0A%20%20y%20%3D%20'enclose'%0A%20%20def%20inner_func()%3A%0A%20%20%20%20z%20%3D%20'local'%0A%20%20%20%20print(x%2C%20y%2C%20z)%0A%20%20inner_func()%0A%20%20%0A%20print(outer_func())%09%23%20global%20enclose%20local" message="" highlight="" provider="manual"/]Local functions can be used when there is need for specialized functions. They also help with code organization and readability.

Closure

The local functions we have looked at so far have no definite way of interacting with the enclosing scope, that is about to change. Local functions can make use of variables in their enclosing scope, the LEGB rule makes this possible.[pastacode lang="python" manual="def%20outer_func()%3A%0A%20%20x%20%3D%205%0A%20%20def%20inner_func(y%20%3D%203)%3A%0A%20%20%20%20return%20(x%20%2B%20y)%0A%20%20return%20inner_func%0Aa%20%3D%20outer_func()%0A%20%0Aprint(a())%09%23%208" message="" highlight="" provider="manual"/]From the above, we see that the inner_func function makes reference to the outer_func fucntion for the value of x. The local function is able to reference the outer scope through closures. Closures maintain references to objects from the earlier scope.Closure is commonly used in what is referred to as Function Factory - these are functions that return other functions. The returned functions are specialized. The Function Factory takes in argument(s), creates local function that creates its own argument(s) and also uses the argument(s) passed to the function factory. This is possible with closures[pastacode lang="python" manual="def%20multiply_by(num)%3A%0A%20%20def%20multiply_by_num(k)%3A%0A%20%20%20%20return%20num%20*%20k%0A%20%20return%20multiply_by_num%0A%20%20%0Afive%20%3D%20multiply_by(5)%0Aprint(five(2))%09%23%2010%0Aprint(five(4))%09%23%2020%0A%20%0Adecimal%20%3D%20multiply_by(10)%0Aprint(decimal(20))%09%23%20200%0Aprint(decimal(3))%09%23%2030" message="" highlight="" provider="manual"/]Here we see that the local function multiply_by_num takes an argument k and returns the argument multiplied by the argument of it's enclosing function. Also, multply_by fuction takes an argument num and returns the function that multiplies num by its argument.LEGB rule does not apply when new name bindings happen[pastacode lang="python" manual="text%20%3D%20%22global%20text%22%0Adef%20outer_func()%3A%0A%20%20text%20%3D%20%22enclosing%20text%22%0A%20%20def%20inner_func()%3A%0A%20%20%20%20text%20%3D%20%22inner%20text%22%0A%20%20%20%20print('inner_func%3A'%2C%20text)%09%23%20inner_func%3A%20global%20text%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20enclosing%20text%0A%20%20inner_func()%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20enclosing%20text%0A%20%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20global%20text%0Aouter_func()%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20global%20text" message="" highlight="" provider="manual"/]In the inner_func function, we have created a new name binding by re-assiging the variable text in the function scope. Calling the inner_func does not affect the variable text in the outer_func, likewise calling outer_func does not affect the global variable text.

global

global is a python keyword that introduces names from global namespace into the local namespace. From the previous code, we can make the inner_func to modify the variable text rather than create a new one.[pastacode lang="python" manual="text%20%3D%20%22global%20text%22%0Adef%20outer_func()%3A%0A%20%20text%20%3D%20%22enclosing%20text%22%0A%20%20def%20inner_func()%3A%0A%20%20%09global%20text%09%23%20binds%20the%20global%20text%20to%20the%20local%20text%0A%20%20%09text%20%3D%20%22inner%20text%22%0A%20%20%09print('inner_func%3A'%2C%20text)%09%23%20inner_func%3A%20inner%20text%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20enclosing%20text%0A%20%20inner_func()%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20enclosing%20text%0A%20%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20global%20text%0Aouter_func()%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20inner%20text" message="" highlight="" provider="manual"/]Since the global keyword has binded the global text variable to the local textvariabel, calling the outer_func function makes changes to the global text, hence, reassigning the variable text in the global scope.

nonlocal

The nonlocal keyword allows us to introduce names from enclosing namespace into the local namespace. Still looking at the previous code:[pastacode lang="python" manual="text%20%3D%20%22global%20text%22%0Adef%20outer_func()%3A%0A%20%20text%20%3D%20%22enclosing%20text%22%0A%20%20def%20inner_func()%3A%0A%20%20%09nonlocal%20text%09%23%20binds%20the%20local%20text%20to%20the%20enclosing%20text%0A%20%20%09text%20%3D%20%22inner%20text%22%0A%20%20%09print('inner_func%3A'%2C%20text)%09%23%20inner_func%3A%20inner%20text%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20enclosing%20text%0A%20%20inner_func()%0A%20%20print('outer_func%3A'%2C%20text)%09%23%20outer_func%3A%20inner%20text%0A%20%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20global%20text%0Aouter_func()%0Aprint('global%3A'%2C%20text)%09%23%20global%3A%20global%20text" message="" highlight="" provider="manual"/]Here, the enclosing text variable changes when the inner_func was called.

Decorators

Since we have understanding of local functions and closure, we can then look at function decorators.Decorators are used to enhance existing functions without changing their definition. A decorator is itself a callable and takes in another callable to return another callable. Simply put - A decorator is a function that takes in another function and returns another function, although it is a bit more than just that.[pastacode lang="python" manual="%23%20Syntax%20for%20decorator%0A%20%0A%40my_decorator%0Adef%20my_function()%3A%0A%20%20pass" message="" highlight="" provider="manual"/]The result of calling my_function is passed into the my_decorator[pastacode lang="python" manual="def%20capitalize(func)%3A%0A%20%20def%20uppercase()%3A%0A%20%20%20%20result%20%3D%20func()%0A%20%20%20%20return%20result.upper()%0A%20%20return%20uppercase%0A%20%0A%40capitalize%0Adef%20say_hello()%3A%0A%20%20return%20%22hello%22%0A%20%20%0Aprint(say_hello())%09%23%20'HELLO'" message="" highlight="" provider="manual"/]The result of calling say_hello is passed into the capitalize decorator. The decorator modifies the say_hello function by changing its result to uppercase. We see that capitalize decorator takes in a callable(say_hello) as an argument and returns another callable(uppercase). This is just a basic example on decoratorIn the next post, we will continue with understanding how decorators work in python. In preparation for that, let's take a brief look at *args and **kwargs

*args and **kwargs

*args allows you to use any number of arguments in a function. You use it when you're not sure of how many arguments might be passed into a function[pastacode lang="python" manual="def%20add_all_arguments(*args)%3A%0A%20%20result%20%3D%200%0A%20%20for%20i%20in%20args%3A%0A%20%20%20%20result%20%2B%3D%20i%0A%20%20return%20result%0A%20%20%20%20%0Aprint(add_all_arguments(1%2C5%2C7%2C9%2C10))%09%23%2032%0Aprint(add_all_arguments(1%2C9))%09%23%2010%0Aprint(add_all_arguments(1%2C2%2C3%2C4%2C5%2C6%2C7%2C8%2C9%2C10))%09%23%2055%0Aprint(add_all_arguments(1))%09%23%201%0Aprint(add_all_arguments())%09%23%200" message="" highlight="" provider="manual"/]Like *args, **kwargs can take many arguments you would like to supply to it. However, **kwargs differs from *args in that you will need to assign keywords[pastacode lang="python" manual="def%20print_arguments(**kwargs)%3A%0A%20%20print(kwargs)%0A%20%20%20%20%0Aprint(print_arguments(name%20%3D%20'Moyosore'))%09%23%20%7B'name'%3A%20'moyosore'%7D%0Aprint(print_arguments(name%20%3D%20'Moyosore')%2C%20country%20%3D%20'Nigeria')%0Aprint(print_arguments())%09%23%20%7B%7D%0A%23%20%7B'name'%3A%20'moyosore'%2C%20'country'%3A%20'Nigeria'%7D%0A%20%0Adef%20print_argument_values(**kwargs)%3A%0A%20%20%20%20for%20key%2C%20value%20in%20kwargs.items()%3A%0A%20%20%20%20%20%20%20%20print('%7B0%7D%3A%20%7B1%7D'.format(key%2C%20value))%0A%20%0Aprint_argument_values(name%3D%22Moyosore%22%2C%20country%3D%22Nigeria%22)%0A%23%20name%3A%20Moyosore%0A%23%20country%3A%20Nigeria" message="" highlight="" provider="manual"/]Args and kwargs can be used together in a function, with args always coming before kwargs. If there are any other required arguments, they come before args and kwargs[pastacode lang="python" manual="def%20add_and_mul(*args%2C%20**kwargs)%3A%0A%20%20pass%0A%20%0Adef%20add_and_mul(my_arg%2C%20*args%2C%20**kwargs)%3A%0A%20%20pass%0A%20%0Adef%20add_and_mul(my_arg%2C%20my_arg_1%2C%20*args%2C%20**kwargs)%3A%0A%20%20pass" message="" highlight="" provider="manual"/]You can do more reading on args and kwargs for better understanding. This is just an example for you to have basic idea on args and kwargs.Next post will be dealing with decorators in details!

Related posts

The latest articles from Andela.

Visit our blog

How to transition your company to a remote environment

Organizations need to embrace a remote-first mindset, whether they have a hybrid work model or not. Remote-first companies can see a boost in more employee morale and productivity. Here’s how to successfully shift to a remote-first environment.

Andela Appoints Kishore Rachapudi as Chief Revenue Officer

Andela scales to meet rising demand among companies to source technical talent in other countries for short or long-term projects.

How Andela's all-female learning program in Lagos is still transforming careers ten years on

In 2014, we launched an all-female cohort in Lagos, Nigeria, to train women in software engineering and development. We spoke to three of the trailblazing women from cohort 4 to find out how the program still inspires their technology careers 10 years later.

We have a 96%+
talent match success rate.

The Andela Talent Operating Platform provides transparency to talent profiles and assessment before hiring. AI-driven algorithms match the right talent for the job.