Introduction

Closures in Python programming language are a powerful tool that allow developers to create functions with persistent data. By closing over variables in an outer function, a closure can maintain state across multiple function calls. Closures can be used to create dynamic and flexible code that can adapt to changing circumstances in a robust and maintainable way. Python makes it easy to create closures, with a clear syntax and intuitive semantics that make them accessible to beginners and experts alike. In this tutorial, we'll explore what closures are, how they work, and how you can use them to improve your Python code.

Table of Contents :

  • Introduction to Closures in Python
  • Use Cases of Closures
    • Function that returns another function
    • Function that takes function as argument 
  • Returning an Inner Function
  • Multi-Scoped Variables
  • Implementing Closures in Python
  • Using Python Closures in a For Loop
  • Problems in using closures

Introduction to Closures in Python :

  • A closure is a function that retains the values of the variables defined in the enclosing scope, even when the outer function has already completed execution.
  • In Python, a closure is created when a nested function references a value from its enclosing scope.
  • Closures are an advanced feature of Python and can help simplify complex code.
  • A closure is a function defined in a non-global scope that references variables from the global scope. 
  • These functions are usually defined in order to encapsulate some functionality, and they can be used to extend the functionality of other functions or modules. 

Use Cases of Closures :

Function that returns another function

  • One common use case for closures is to define a function that takes one or more arguments and returns a new function that is bound to those arguments. 
  • For example, the following code defines a function that takes a single argument and returns a new function that squares that argument: 
  • Code Sample :

def square(x): 
	def _(y): 
		return x * y 
	return _ 

This function can be used as follows: 
square_of_two = square(2) 
square_of_two(4) 16 
square_of_three = square(3) 
square_of_three(4) 12 

  • In this example, the square function is used to create two new functions,  square_of_two  and  square_of_three
  • These new functions are closures, because they reference the x variable from the square function's scope. 

Function that takes function as argument 

  • Another common use case for closures is to define a function that takes a function as an argument and returns a new function that wraps the original function with some additional functionality. 
  • For example, the following code defines a function that takes a function as an argument and returns a new function that prints the return value of the original function: 
  • Code Sample :

def print_return_value(func): 
	def _(*args, **kwargs): 
		result = func(*args, **kwargs) 
		print(result) 
		return result 
		
return _ 

This function can be used as follows: 

>>> @print_return_value ... def square(x): ... return x * x 
>>> square(2) 4 4 

  • In this example, the  print_return_value  function is used as a decorator to wrap the square function. The resulting square function is a closure, because it references the  func  variable from the  print_return_value  function's scope. 

Returning an Inner Function :

  • A closure is created by returning a nested function from an outer function in Python.
  • The inner function has access to the variables defined in the outer function even after the outer function has completed execution.
  • Here is a code example that demonstrates returning an inner function in Python:
  • Code Sample :

 def outer_function(x):
     def inner_function(y):
         return x * y
     return inner_function
 closure_function = outer_function(10)
 result = closure_function(5)
 print(result) # Output: 50

Multi-Scoped Variables :

  • In Python, a closure stores closed-over variables in a dictionary-like data structure called a cell.
  • When a variable is referenced in a closure, Python looks up the variable in the cell.
  • This cell can also store multi-scoped variables, which are values assigned to a variable in a closure that also exist in the enclosing function.
  • Here is a code example that demonstrates multi-scoped variables in Python:
  • Code Sample :

 def outer_function(x):
     count = x
     def inner_function(y):
         nonlocal count
         count += y
         return count
     return inner_function
 closure_function = outer_function(10)
 result = closure_function(5)
 print(result) # Output: 15

Implementing Closures in Python :

  • A closure can access any variable in its enclosing scope, whether that variable is part of the arguments or not.
  • When a closure is returned from a function, it captures the entire state of the enclosing function.
  • Closures can be used to provide encapsulation and reduce coupling between functions.
  • Here is a code example that demonstrates implementing a closure in Python:
  • Code Sample :

 def counter():
   count = 0
   def inner():
       nonlocal count
       count += 1
       return count
   return inner
 
 closure_function = counter()
 result = closure_function()
 print(result) # Output: 1

Using Python Closures in a For Loop :

  • Closures can be used to generate functions dynamically in a for loop.
  • Each function generated in this way has its own closed-over variable state.
  • Here is a code example that demonstrates using closures in a for loop in Python :
  • Code Sample :

 funcs = []
 for i in range(5):
     def multiply_by(x):
         return i * x
     funcs.append(multiply_by)
 for f in funcs:
     print(f(2)) # Output: 8 8 8 8 8

Problems in using closures :

  • Closures can be a powerful tool for extending the functionality of other functions or modules, but they should be used with caution. 
  • One potential problem with closures is that they can create dependencies between the code that defines the closure and the code that uses the closure. 
  • For example, consider the following code: 
  • Code Sample :

def make_counter(): 
count = 0 
def _(): 
	global count 
	count += 1 
	return count 

return _ 

This function defines a closure that increments a global variable and returns the new value. 
This closure can be used as follows: 
counter1 = make_counter() 
counter2 = make_counter() 
counter1() 1 
counter1() 2 
counter2() 1 


  • In this example, two counters are created using the  make_counter()  function. 
  • The first counter works as expected, but the second counter doesn't work correctly. 
  • This is because the  make_counter()  function creates a closure that references the global  count  variable. 
  • This means that the two counters are actually sharing the same count variable, which explains why the second counter doesn't work correctly. 
  • This problem can be avoided by using a non-local variable instead of a global variable: 
  • Code Sample :

def make_counter(): 
count = 0
def _(): 
	nonlocal count 
	count += 1 
	return count 

return _ 

Now the two counters will work correctly: 
counter1 = make_counter() 
counter2 = make_counter() 
counter1() 1 
counter1() 2 
counter2() 1 


Prev. Tutorial : functions as objects

Next Tutorial : Decorators