Back to Course

Intermediate Python

0% Complete
0/0 Steps

Closures are a powerful concept in programming that allow a function to remember and access the variables defined in its enclosing scope, even after the enclosing function has finished executing.

Creating a Closure

To create a closure in Python, you define a nested function inside another function, and use the nested function after the enclosing function has returned. The nested function has access to the variables defined in the enclosing function, even if the enclosing function is no longer in memory.

For example:

def outer_function(x):
    def inner_function():
        print(x)
    return inner_function

closure = outer_function(10)
closure()  # Output: 10

In this example, the inner_function() is a closure that remembers the value of x defined in the outer_function(). When the closure() function is called, it prints the value of x.

Closures can be used to create functions with different behavior, without having to define a separate function for each behavior. For example:

def make_multiplier(x):
    def multiplier(y):
        return x * y
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))  # Output: 10
print(triple(5))  # Output: 15

In this example, the make_multiplier() function creates a closure that multiplies its argument by a specified value. The double() and triple() functions are created by calling make_multiplier() with different values for x.

Closures can also be used to preserve the state of a function between calls. For example:

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

In this example, the make_counter() function defines a closure that increments a counter variable each time it is called. The counter variable is preserved between calls to the counter() function, allowing it to keep track of the number of times it has been called.

Exercises

To review these concepts, we will go through a series of exercises designed to test your understanding and apply what you have learned.

Use a closure to create a function that returns the next value in the Fibonacci sequence each time it is called.

def make_fibonacci():
    a, b = 0, 1
    def fibonacci():
        nonlocal a, b
        a, b = b, a + b
        return a
    return fibonacci

fib = make_fibonacci()
print(fib())  # Output: 1
print(fib())  # Output: 1
print(fib())  # Output: 2
print(fib())  # Output: 3
print(fib())  # Output: 5

Use a closure to create a function that returns the nth power of a number.

def make_power(n):
    def power(x):
        return x ** n
    return power

square = make_power(2)
print(square(3))  # Output: 9

cube = make_power(3)
print(cube(3))  # Output: 27

quadruple = make_power(4)
print(quadruple(3))  # Output: 81

Use a closure to create a function that returns the next value in an arithmetic progression each time it is called.

def make_arithmetic_progression(start, step):
    def arithmetic_progression():
        nonlocal start
        result = start
        start += step
        return result
    return arithmetic_progression

progression = make_arithmetic_progression(0, 2)
print(progression())  # Output: 0
print(progression())  # Output: 2
print(progression())  # Output: 4
print(progression())  # Output: 6

Use a closure to create a function that returns the next value in a geometric progression each time it is called.

def make_geometric_progression(start, factor):
    def geometric_progression():
        nonlocal start
        result = start
        start *= factor
        return result
    return geometric_progression

progression = make_geometric_progression(2, 3)
print(progression())  # Output: 2
print(progression())  # Output: 6
print(progression())  # Output: 18
print(progression())  # Output: 54

Use a closure to create a function that returns the next value in a geometric progression, with a maximum value.

def make_bounded_geometric_progression(start, factor, maximum):
    def bounded_geometric_progression():
        nonlocal start
        result = start
        start *= factor
        if start > maximum:
            start = maximum
        return result
    return bounded_geometric_progression

progression = make_bounded_geometric_progression(2, 3, 50)
print(progression())  # Output: 2
print(progression())  # Output: 6
print