Closures and Decorators demystified in Python
Closures and Decorators are very tricky and essential tools in Python programming language to learn whether you are a Web developer of a Data scientist. In this Article, I will explain what a closure and decorator is using simple examples. Before actually going into Closures and decorators let’s see how scope works in Python programming language
Scope in Python
Scope in general and in every programming language means the area of a program in which we can access variables, functions, objects and so on. By using Scope, Accessing any name(name can be anything like Variable, function or object and so on) depends upon where you have defined that name. In general there are two types of scope
- Global scope : Names defined in this scope can be access from anywhere in the code
- Local Scope : Accessing names defined in this scope is restricted to that scope only(functions, For loops etc.)
LEGB Rule
Python uses LEGB principle to resolve any name defined in a code block. L stands for Local , E stands for Enclosed , G stands for Global and B stands for Built-in
- Local or Function Scope : This scope contains the names defined in a function block. these names will be only accessible within that function and if we try to access any name outside that function scope python will throw an error. Names defined under function scope will become active only when we call that function and dies after function execution
- Enclosed Scope : This scope contains names defined inside a function but outside of any nested function. Means this scope will only come into picture if we have nested functions(Function defined under another function). names defined in this scope will be accessible to both outer function(local scope) as well as nested functions. For nested functions these variables are called Free Variables means these names are not defined inside nested function but they are available in nested function.
- Global Scope : This is high level scope in any python code block. any names defined in this scope are available at every part of the program
- Built-in scope : This is the place where python built-in functions , classes and other attributes that are built into Python. Names defined in this scope will be automatically loaded whenever we run a python script and can be accessed from anywhere in the code.
So we can conclude that LEGB principle will be used by python in order to find any name. Have a look at below code for better understanding
a=[100,-10,89,0,-100,78] # a is defined in Global Scope def do_sort():
print(sorted(a))do_sort() # [-100,-10,0,78,89,100]
In the above code, Whenever python encounters do_sort() method execution it found a Variable a . At this point, Python search for variable a inside function scope(Local to that function). Here there is no variable a defined inside the function. As do_sort() is not a nested function, Enclosed Scope will not come into picture here. Then Python will try to find any variable with name a global scope and yes we have a variable a defined in the global scope. Python will pass this variable to sorted() which is a Built-in method which takes an Iterable and sort it.
The same flow will be performed for sorted() function. Python will first search if there is any method defined in Global scope with name sorted()(It is not recommended to use user-defined function name as same as pre-defined function names). We do not have any user-defined method as sorted() in global scope. So python will now search in Built-in scope and found out one.
Closures
Now we have basic idea of scope and how python resolves a Name using LEGB principle. Now let’s look at what exactly mean by Closure. A Closure is a technique by which some data is associated with code block. In python this is possible by using inner or nested functions. So a closure is a nested function which remembers the variables available freely (Variables from its enclosing Scope) to it even though its enclosing scope finishes execution.
def take_greet(msg):
message=msg # message is the variable local to function take_greet()
def generate_greet(): # Nested Function
print(message) # Here message is free variable available to function generate_greet()
return generate_greet #returning inner function
display_greet=take_greet("Good Morning!")
display_greet() # Good Morning!
In above code block, We defined take_greet() function which takes message to be printed. Also we have another function which is nested inside take_greet() function. Below are the points we need to make a note regarding above code block
- Incoming message is stored in Variable message which is local to take_greet() function
- message variable is also available to nested function generate_greet() because this message variable is defined in enclosing scope of generate_greet() function.
- take_greet() function returns inner function generate_greet() and stores in variable display_greet . By this time execution of take_greet() function is completed but once we executed display_greet() which is nothing but inner function which is ready to be executed still is able to fetch variable message and prints it to console
- Now display_greet variable is nothing but the nested function generate_greet() which is ready to be executed. it displays Good Morning text once executed
Now we can create as many instances of inner function with different input texts and re-use it wherever we want because inner function remembers its free variable even though its enclosing scope completed its execution.
display_good_afternoon=take_greet("Good Afternoon!")
display_good_afternoon() # Good Afternoon_
Decorators
The most interesting implementation of closures are decorators. A decorator is a function which takes another function as argument and decorate it(Modifies or add additional functionality) without changing the original source code of the function which passed as argument and returns a new function with added functionality. Let’s see a very basic template how decorator look like.
- In above example, display_message() function is the original function for which we want to add some functionality(Can be anything) without actually changing its function body. So we have passed display_message() function to decorator_function() in line 12
- As every decorator function returns a new function with added functionality to original function. So we have another nested function named wrapper_function() in which we will modify or add functionality to our original function and finally returns the nested function. wrapper_function() will have access to original function just as like a free variable we have passed for closure previously.
- Nested functions takes args and kwargs which are used to pass any arguments for the Original function if required.
- Finally wrapper_function() is returned and stored in Variable display_message_wrapper. Now if we execute display_message_wrapper which is nothing but wrapper_function(), Eventually Original function will be executed with the given arguments to display_message_wrapper as in Line 13
So, We are adding some functionality to display_message() function without actually changing its body. In above code, Line 12 and 13 are Optional and we can do something like below which yields the same result
One Line 7, We are adding @decoratorfunctionname(This function name can be anything) so that python will input the function which is underneath to the decorator function specified using @ symbol and on Line 12 we are calling display_message() function with required arguments.
Let’s discuss a real world example where decorators are shine. Let’s say you are a trader and you have say 80,000 shares of a ABC company. And whenever you want to sell your shares, Software needs to check whether the quantity of shares your selling now is less than your total shares of ABC company then only software will be able to deduct the quantity you are selling from your total shares.
Now we need to write a code for this and constraint is that we can be able to use this code for multiple users without repeating the code. In these scenarios we can use the decorators functionality instead of going for a Class. So decorators in python provides a way to re-use the code.
Here sell shares() is the original function which we are passing to decorator function. Inside decorator function, We are checking if sell quantity is greater than what he actually holds. If yes, returning false else executing the original function with necessary arguments. Now we can use this decorator function for any other trades once we know his/her total holdings.
These are very simple examples where decorators are useful. Decorators are also used in Flask which is used to build web applications using python and knowing how they work under the hood is very essential