In This Article
Closures in Python are created when a nested function references a variable from its containing function. The inner function has access to its own local scope, the scope of the outer function, and even the global scope.
It “closes over” the variables it needs, hence the name closure. This behavior enables functions to have private, encapsulated data that can be accessed and manipulated through the closure.
One of the main benefits of closures is their ability to create and return specialized functions on the fly. This is particularly useful when we want to define functions that have certain behavior but need to be parameterized with different values.
Python Closures provides a way to generate these customized functions without having to define them explicitly.
Python Nested Functions
In Python, nested functions refer to the concept of defining a function inside another function. This means that a function is declared and defined within the body of another function, creating a nested or inner function.
The nested function can access variables and attributes from its enclosing function’s scope, as well as the global scope.
Example of nested function Python :
def outer_function():
x = 10
def inner_function():
y = 5
result = x + y
print(f"The result is {result}")
inner_function()
outer_function()
Here, we have an outer_function
that defines a local variable x
with a value of 10.
Inside outer_function
, we have another function called, which defines its own local variable y
with a value of 5.
The inner_function
accesses the x
variable from its enclosing scope and performs an addition operation with y
.
Finally, it prints the result. When we call outer_function
, it invokes the inner_function
as well, resulting in the output: “The result is 15”.
Python Closures
A closure is a function object that remembers values in the enclosing scope even if they are not present in memory.
It allows a nested function to access and retain the values of variables defined in its enclosing function, even after the outer function has finished executing.
This behavior makes closures incredibly powerful for maintaining state and creating functions with persistent data.
Example 1: Counter using closures
def counter():
count = 0
def increment():
nonlocal count
count += 1
print(count)
return increment
# Create two separate counter instances
counter1 = counter()
counter2 = counter()
counter1() # Output: 1
counter1() # Output: 2
counter2() # Output: 1
Here, the counter
function returns the increment
function. The count
variable is defined in the counter
function’s scope and is accessible within the increment
function due to the closure.
Each time increment
is called, it increments the count
and prints the updated value.
The closure allows the count
variable to retain its value between multiple calls to the increment
function, resulting in separate counters for counter1
and counter2
.
Example 2: Function factory using closures
def multiply_by(n):
def multiplier(x):
return x * n
return multiplier
# Create a function that multiplies by 5
multiply_by_5 = multiply_by(5)
result = multiply_by_5(10)
print(result) # Output: 50
Here, the multiply_by
function takes a parameter n
and returns the multiplier
function. The multiplier
function is a closure that remembers the value of n
even after the multiply_by
function has been completed. The returned multiplier
function multiplies its input x
by the value of n
, effectively creating a specialized function that multiplies by a specific number.
Use of Closures in Python
- Data Encapsulation: Closures enable data encapsulation by allowing inner functions to access and modify variables from their enclosing scope. This can be used to create private variables and control access to them.
def create_counter():
count = 0
def increment():
nonlocal count
count += 1
print(count)
def decrement():
nonlocal count
count -= 1
print(count)
return increment, decrement
inc, dec = create_counter()
inc() # Output: 1
inc() # Output: 2
dec() # Output: 1
- Function Factories: Closures allow for the creation of function factories, where a function generates and returns customized functions based on certain parameters or configurations.
def multiply_by(n):
def multiplier(x):
return x * n
return multiplier
multiply_by_5 = multiply_by(5)
result = multiply_by_5(10)
print(result) # Output: 50
- Memoization: Python Closures can be used to implement memoization, a technique that caches the results of expensive function calls to improve performance by avoiding redundant computations.
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(10)) # Output: 55
- Decorators: Closures form the basis for creating decorators, which are functions that modify or enhance the behavior of other functions without modifying their source code directly.
def logger(func):
def wrapper(*args, **kwargs):
print(f"Calling function: {func.__name__}")
result = func(*args, **kwargs)
print(f"Function {func.__name__} returned: {result}")
return result
return wrapper
@logger
def add(a, b):
return a + b
result = add(2, 3) # Output: Calling function: add
# Function add returned: 5