Programming
Python Closures: Remembering Values from Enclosing Scopes
Closures are a powerful feature in Python that allows an inner function to access variables from its surrounding (enclosing) scope, even after the outer function has finished executing. Understanding closures is essential for grasping concepts like decorators and functional programming in Python.
Ryan McBride
Ryan McBride
alt

Source: Jp Valery on Unsplash

1. What is a Closure?

In Python, a closure is characterized by the following:

  • An Inner Function: A closure involves a function defined inside another (outer) function.
  • Remembering its Environment: The inner function "remembers" and retains access to variables from the local scope of its outer function where it was created.
  • Access to Free Variables: The inner function can access variables that are not defined within itself but are accessible in the outer function's scope. These are called "free variables" within the inner function.
  • Persistence After Outer Function Finishes: The inner function can still access these captured variables even after the outer function has completed its execution.

In essence, a closure is an inner function that "closes over" and remembers the variables from its surrounding environment, even when the outer function is no longer running.

2. Example of a Closure


    def outer_function(msg):
        message = msg

        def inner_function():
            print(message)

        return inner_function

    hi_func = outer_function("Hi")
    hello_func = outer_function("Hello")

    hi_func()   # Output: Hi
    hello_func() # Output: Hello
   

Explanation:

  • The outer_function takes an argument msg and defines a local variable message with this value.
  • Inside outer_function, another function inner_function is defined. This inner_function accesses the message variable from the enclosing scope of outer_function. The message variable is a "free variable" within inner_function because it's not defined within inner_function itself.
  • The outer_function returns the inner_function.
  • When we call outer_function("Hi"), it returns the inner_function with the message variable "closed over" the value "Hi". This returned function is assigned to hi_func.
  • Similarly, hello_func becomes the inner_function with "message" closed over "Hello".
  • Even after outer_function has finished executing, the returned inner_function (now referenced by hi_func and hello_func) still retains access to the message variable from its creation environment. When we call hi_func() and hello_func(), they print the respective "closed over" messages.

3. Practical Use Cases

3.1. Creating Function Factories

Closures are often used to create function factories, where you generate functions with pre-configured behavior.


    def exponent_function(n):
        def raise_to_power(x):
            return x ** n
        return raise_to_power

    square = exponent_function(2)  # Create a function to square numbers
    cube = exponent_function(3)    # Create a function to cube numbers

    print(square(5))  # Output: 25
    print(cube(5))    # Output: 125
   

3.2. Decorators

Closures are a fundamental part of Python decorators, which provide a way to modify or enhance the behavior of functions or methods.


    def my_decorator(func):
        def wrapper_function(*args, **kwargs):
            print("Before calling the function.")
            result = func(*args, **kwargs)
            print("After calling the function.")
            return result
        return wrapper_function

    @my_decorator
    def say_hello(name):
        return f"Hello, {name}!"

    print(say_hello("Alice"))
    # Output:
    # Before calling the function.
    # After calling the function.
    # Hello, Alice!
   

3.3. Maintaining State

Closures can be used to maintain state across multiple calls to a function.


    def counter():
        count = 0
        def increment():
            nonlocal count  # Allows modification of 'count' in the enclosing scope
            count += 1
            return count
        return increment

    my_counter = counter()
    print(my_counter())  # Output: 1
    print(my_counter())  # Output: 2
    print(my_counter())  # Output: 3
   

4. Benefits of Using Closures

  • Data Hiding: The variables used in the outer function are protected from global access, providing a form of data encapsulation.
  • Function Factories: Closures enable the creation of functions that are customized based on the environment in which they are created.
  • Elegant Code: Closures can make code more concise and readable, especially when used in decorators and functional programming.