Understanding Closures in Python : A Comprehensive Guide with Examples

Understanding Closures in Python

Introduction

Closures are an important concept in Python that allow functions to preserve their execution context. This enables powerful constructs like factories, decorators, and helps impart state to functions. In this tutorial, we will learn about closures in detail with simple examples.

What are Closures in Python?

A closure is an inner or nested function that carries information about its enclosing scope, even when invoked outside of that scope. The enclosed scope consists of any non-global variables available in the enclosing function at the time the nested function was defined.

This is possible due to late binding in Python. The values of variables used in closures are looked up dynamically at runtime rather than being fixed at compile time.

A Simple Example of Closure

Let’s look at a basic example to understand closures:

def print_msg(msg):

  def printer():    
    # printer() is a closure
    print(msg)

  printer()

print_msg("Hello") # Output: Hello

Here print_msg() is the outer enclosing function while printer() is the nested function that forms the closure.

The closure printer() captures the msg variable from the enclosing scope of print_msg(). When print_msg() is invoked, the value “Hello” gets bound to msg.

The closure printer() retains its binding to msg even when called outside of print_msg(). Hence it correctly prints the message.

Example 2: Counter using a Closure

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

counter1 = counter()
counter2 = counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter2())  # 1

Example 3: Custom Sorting with Closures

def custom_sort(key_func):
    def sort_list(lst):
        return sorted(lst, key=key_func)
    return sort_list

students = [
    {'name': 'Alice', 'score': 85},
    {'name': 'Bob', 'score': 92},
    {'name': 'Charlie', 'score': 78}
]

sort_by_name = custom_sort(lambda x: x['name'])
sort_by_score = custom_sort(lambda x: x['score'])

sorted_by_name = sort_by_name(students)
sorted_by_score = sort_by_score(students)

print(sorted_by_name)
print(sorted_by_score)

Returning Closures from Functions

Closures can be returned from functions as well:

def power_factory(exp):
  
  def pow_gen(base):
    return base ** exp

  return pow_gen # Returns a closure

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

print(square(5)) # 25 
print(cube(2)) # 8

Here power_factory() returns a closure pow_gen that captures the exp argument. This gets bound to 2 and 3 respectively when called. The closures square and cube retain their binding and generate the right powers.

When Variables Get Captured

It is important to note that closures capture variables, not values. If a variable binding changes between defining and calling the closure, the current binding is captured:

msg = "Hello"

def printer():
  print(msg) 

printer() # Hello

msg = "World"
printer() # World

Since msg changed before calling printer() again, the new value got captured.

Uses of Closures

Some common uses of closures are:

  • Parameterizing functions e.g. factories
  • Saving state between function calls
  • Implementing decorators
  • Creating custom generators

Overall they allow crafting functions that are more intelligent while avoiding use of global state.

Key Benefits of Closures

  • Avoid expensive re-creation of dynamic contexts
  • Reduce code repetition through factories
  • Enable better state retention
  • Impart modularity by encapsulating logic

Closures in Python are a powerful feature that is useful across many problem domains.

Conclusion

Closures in python allow functions to retain and access their creation context. This provides continuity between calls and helps reduce global state reliance. In Python, functions dynamically capture variables rather than values at definition time due to late binding. Closures in python are useful for parameterization via factories, state retention, encapsulation, and other applications. They showcase Python’s support for powerful functional programming constructs in an elegant way.

Frequently Asked Questions

  1. What is a closures in Python?

A closure is an inner or nested function that remembers values in the enclosing scope even when invoked externally. The nested function retains access to the enclosing scope and its non-global variables.

  1. How do closures work in Python?

Python implements closures via late binding. The values of outer scope variables are looked up dynamically at call time rather than being fixed at compile time. This allows the inner function to capture the runtime context.

  1. When are closures created in Python?

Closures are created whenever a nested function references one or more variables from its enclosing function scope. The nested function and its bindings get defined and stored to create the closure.

  1. What are some common uses of closures in Python?

Typical uses of closures include parameterizing functions via factory methods, retaining state between calls to simulate private methods, implementing decorators, creating custom generators etc.

  1. What are the benefits of using closures in Python?

Benefits include avoiding expensive re-creation of dynamic function contexts, reducing code repetition through factories, enabling better state retention between calls, and encapsulating logic neatly.

  1. What gets captured in a closure in Python?

A closure captures variable bindings rather than values. The current binding value is captured at the time of call dynamically per late binding semantics.

  1. Can closures modify enclosing scope variables in Python?

Yes, closures can modify the bindings of outer scope variables since they have access to the enclosing state. This allows using closures to update state.

  1. Do closures capture global variables in Python?

Closures only capture non-global variables from their enclosing function scope. Global variables remain global even within closures.

  1. What is a closure disadvantage in Python?

Retaining enclosing scope references can result in extra memory usage if the closures outlive the enclosing function.

  1. How can factory methods use closures for parameterization?

The factory returns a parameterized closure capturing factory arguments, while callers get appropriately configured functions.

Leave a Reply

Your email address will not be published. Required fields are marked *