Skip to main content

7.5 - Closures

7.5.1 - Introduction

Closures in Python are a feature that allows a function to remember the environment in which it was created. This means a closure is a function object that has access to variables in its lexical scope, even when the function is called outside that scope.


7.5.2 - Basic Syntax of Closures

In Python, a closure is created by defining a function inside another function, and then returning the inner function. The inner function will have access to the local variables of the outer function, even after the outer function has finished executing.

def outer_function(msg):
message = msg

def inner_function():
print(message)

return inner_function

my_closure = outer_function("Hello, World!")
my_closure() # Output: Hello, World!

7.5.3 - Creating Your Own Closure

Here's an example of how to create and use a closure in Python.

def multiplier_of(n):
def multiplier(number):
return n * number
return multiplier

times3 = multiplier_of(3)
times5 = multiplier_of(5)

print(times3(9)) # Output: 27
print(times5(3)) # Output: 15

7.5.4 - Closures Case Studies

7.5.4.1 - Case Study 1: Logging Function Calls

Closures can be used to create decorators, like a simple logging decorator to monitor function calls.

def logger(func):
def log_func(*args):
print(f'Running "{func.__name__}" with arguments {args}')
print(func(*args))
return log_func

def add(x, y):
return x + y

def sub(x, y):
return x - y

add_logger = logger(add)
sub_logger = logger(sub)

add_logger(3, 3) # Output: Running "add" with arguments (3, 3) \n 6
sub_logger(10, 5) # Output: Running "sub" with arguments (10, 5) \n 5

7.5.4.2 - Case Study 2: Maintaining State

Closures can maintain state between function calls.

def counter():
count = 0
def inner():
nonlocal count
count += 1
return count
return inner

my_counter = counter()

print(my_counter()) # Output: 1
print(my_counter()) # Output: 2

7.5.4.3 - Case Study 3: Data Hiding

Closures can be used for data hiding and encapsulation.

def data_hiding():
private_data = "Secret Data"
def inner():
print(private_data)
return inner

reveal = data_hiding()
reveal() # Output: Secret Data

7.5.4.4 - Case Study 4: Function Factories

Closures can be used to create function factories, which are functions that return new, specialized functions.

def power_factory(exponent):
def power(base):
return base ** exponent
return power

square = power_factory(2)
cube = power_factory(3)

print(square(4)) # Output: 16
print(cube(3)) # Output: 27

In this example, power_factory creates two different functions, square and cube, that are specialized versions of the power function.

7.5.4.5 - Case Study 5: Caching with Closures

Closures can be used to implement simple caching strategies, like memoization, to optimize function calls.

def memoize(func):
cache = {}
def memoized_func(*args):
if args in cache:
return cache[args]
result = func(*args)
cache[args] = result
return result
return memoized_func

@memoize
def long_running_function(num):
# Simulate a long-running computation
from time import sleep
sleep(2)
return num * num

print(long_running_function(4)) # Takes 2 seconds
print(long_running_function(4)) # Instantaneous, uses cached value

In this case, memoize is a closure that encapsulates a cache dictionary. The memoized_func checks the cache to avoid recalculating results for previously processed inputs, thereby reducing execution time for repetitive calls.