3.1 Intro to Closures
A closure is a function that captures variables from its surrounding scope, even after that scope has finished executing. This means that the closure can "remember" the values of variables from its external scope and keep working with them even when that scope is no longer active.
To get how closures work, let's check out the following example:
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(10)
print(closure(5)) # Output: 15
Let's break down what's going on here:
Outer function (outer_function)
: This function takes
an argument x
and defines an inner function inner_function
,
which takes an argument y
and returns the sum of x
and y
.
The inner_function
is only declared, not called within
outer_function
.
Inner function (inner_function)
: This function
is returned from outer_function
and internally keeps
a reference to the value of x
that was passed to outer_function
.
Closure: The variable closure
becomes
a closure that "remembers" the value of x
(here, 10) and can
use it when called.
Usually, no one gets closures perfectly the first time. So, let's boost your understanding of closures with some examples.
3.2 Examples of Using Closures
Creating a Generator Function
Closures can be used to create generator functions that generate sequences of values.
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
counter = make_counter()
print(counter()) # Output: 1
print(counter()) # Output: 2
print(counter()) # Output: 3
Explanation:
Generator function (make_counter)
: This function creates
a variable count
and returns the inner function counter
, which
increments the value of count
and returns it.
Closure: The function counter
maintains the state of
the variable count
and can modify it with each call.
Creating a Function with Configuration
Closures can be used to create functions with pre-defined configurations.
def make_multiplier(factor):
def multiplier(x):
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
Explanation:
Configurator function (make_multiplier)
: This function
takes a multiplier factor
and returns the inner function multiplier
,
which multiplies the input value by factor
.
Closures: The functions double
and triple
are closures
that keep their own factor
values and use them for
multiplying.
Filtering Data with Parameters
Closures can be handy for creating filtering functions with parameters.
def make_filter(threshold):
def filter_func(value):
return value > threshold
return filter_func
filter_above_10 = make_filter(10)
data = [5, 10, 15, 20]
filtered_data = list(filter(filter_above_10, data))
print(filtered_data) # Output: [15, 20]
Explanation:
Filter function (make_filter)
: This function takes
a threshold value threshold
and returns the inner function filter_func
,
which checks if a value is above the threshold.
Closure: The function filter_func
stores the threshold
value and uses it for filtering data.
3.3 Pros and Cons of Closures
Benefits of Using Closures
State Encapsulation: Closures allow you to encapsulate state within a function, avoiding global variables and improving code readability and maintenance.
Flexibility: Closures can be used to create functions with specific configurations or behaviors, making the code more flexible and adaptable.
Functional Programming: Closures are a key concept in functional programming, allowing for higher-order functions and other functional constructs.
Drawbacks and Potential Issues
Despite many benefits, closures have their limitations:
Memory Usage: Closures can hold references to objects that are no longer needed, which can lead to memory leaks.
Debugging Complexity: Closures can make debugging harder, as the state of variables might not be obvious.
GO TO FULL VERSION