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.