> For the complete documentation index, see [llms.txt](https://python-codelivly.gitbook.io/python-mastery-from-beginner-to-expert/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://python-codelivly.gitbook.io/python-mastery-from-beginner-to-expert/advance-python/closures.md).

# Closures

A closure is a function object that has access to variables in its enclosing lexical scope, even when the function is called outside that scope. In simpler terms, a closure is a function that remembers the values of the variables that were present in the enclosing scope at the time of its creation.

Closures are used in many programming languages, including Python, to implement various programming patterns. One of the most common uses of closures is to create function factories or generators.

A closure is created when a nested function references a value from its enclosing function's scope. The nested function can access and modify the values of the enclosing function's variables, even after the enclosing function has returned.

Closures are useful in situations where you want to create a function that maintains a state across multiple calls. For example, you might use a closure to create a counter function that counts the number of times it has been called, or a memoization function that remembers the results of expensive function calls to avoid recalculating them.

In Python, closures are created using nested functions. The nested function can access and modify the values of the enclosing function's variables using the `nonlocal` keyword. When the enclosing function returns, the closure function retains a reference to the values of the enclosing function's variables.&#x20;

## Creating Closures in Python&#x20;

In Python, closures can be created using nested functions. Here are two common ways to create closures:

1. Using Nested Functions:

A closure is created when an inner function references a value from an outer function's scope. Here's an example of a closure that adds a given value to a number:

```python
def adder(x):
    def inner(y):
        return x + y
    return inner

add_5 = adder(5)
print(add_5(10))  # Output: 15
```

In this example, the `adder()` function returns the `inner()` function, which takes a parameter `y` and adds it to the value of `x`. The `add_5` variable is assigned the closure returned by `adder(5)`, which means it remembers the value of `x` as 5. When `add_5(10)` is called, it adds 10 to 5 and returns 15.

2. Using Function Decorators:

Function decorators are a powerful feature in Python that allow you to modify the behavior of a function. A function decorator is a function that takes another function as input and returns a new function as output. Here's an example of a closure that uses a function decorator:

```python
def adder(x):
    def decorator(func):
        def inner(y):
            return func(x + y)
        return inner
    return decorator

@adder(5)
def multiply_by_two(x):
    return x * 2

print(multiply_by_two(10))  # Output: 30
```

In this example, the `adder()` function returns a decorator function that takes another function (`multiply_by_two`) as input and returns a new function (`inner`) as output. The `inner()` function takes a parameter `y`, adds it to the value of `x`, and passes the result to the original function (`multiply_by_two`). The `@adder(5)` syntax applies the closure returned by `adder(5)` to the `multiply_by_two` function, so when `multiply_by_two(10)` is called, it first adds 5 to 10 (to get 15) and then multiplies it by 2 to get 30.&#x20;

## Examples of Closures&#x20;

Here are some examples of closures in Python:

1. Counter:

A closure can be used to create a counter function that counts the number of times it has been called. Here's an example:

```python
def counter():
    count = 0
    def inner():
        nonlocal count
        count += 1
        print("Count:", count)
    return inner

c = counter()
c()  # Output: Count: 1
c()  # Output: Count: 2
c()  # Output: Count: 3
```

In this example, the `counter()` function returns a closure that remembers the value of `count` as 0. Every time the closure is called, it increments the value of `count` by 1 and prints the current value of `count`.

2. Average:

A closure can also be used to create a function that calculates the average of a series of numbers. Here's an example:

```python
def average():
    numbers = []
    def inner(num):
        numbers.append(num)
        avg = sum(numbers) / len(numbers)
        print("Average:", avg)
    return inner

a = average()
a(10)  # Output: Average: 10.0
a(20)  # Output: Average: 15.0
a(30)  # Output: Average: 20.0
```

In this example, the `average()` function returns a closure that remembers the list of numbers in the `numbers` variable. Every time the closure is called with a new number, it adds the number to the list, calculates the average of the numbers in the list, and prints the current average.

3. Memoization:

A closure can also be used to implement memoization, which is a technique that stores the results of expensive function calls and returns the cached result when the same inputs occur again. Here's an example:

```python
def memoize(func):
    cache = {}
    def inner(n):
        if n not in cache:
            cache[n] = func(n)
        return cache[n]
    return inner

@memoize
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # Output: 55
```

In this example, the `memoize()` function is a closure that takes a function `func` as input and returns a new function `inner` as output. The `inner()` function checks whether the result of `func(n)` has already been computed and cached in the `cache` dictionary. If it has, `inner()` returns the cached result; otherwise, it calls `func(n)`, caches the result, and returns it. The `@memoize` syntax applies the memoization closure to the `fibonacci()` function, which calculates the nth Fibonacci number recursively. The first time `fibonacci(10)` is called, it computes the value using the recursive algorithm and caches the results of all the previous function calls. The second time `fibonacci(10)` is called, it simply returns the cached value without recomputing it, resulting in faster execution time.&#x20;

## Using Closures with Decorators&#x20;

Using closures with decorators allows you to modify the behavior of a function by adding additional functionality before or after its execution. Here's an explanation of how to use closures with decorators:

### Introduction to Decorators:

Decorators are functions that take another function as input and extend its functionality without modifying its source code. They are denoted by the `@` symbol followed by the decorator function name placed above the function definition. Decorators provide a concise and flexible way to enhance the behavior of functions.

### Creating Decorators with Closures:

Closures can be used to create decorators by defining an inner function within the decorator function. The inner function acts as the closure and can access the original function's arguments and return value. Here's an example:

```python
def decorator(func):
    def inner(*args, **kwargs):
        # Additional functionality before the original function
        print("Decorating...")
        
        # Call the original function
        result = func(*args, **kwargs)
        
        # Additional functionality after the original function
        print("Finished decorating!")
        
        # Return the result of the original function
        return result
    
    # Return the closure
    return inner
```

In this example, the `decorator()` function takes another function (`func`) as input and returns the closure `inner()`. The `inner()` function performs additional operations before and after calling the original function (`func`). It uses the `*args` and `**kwargs` syntax to accept any number of positional and keyword arguments that the original function may have. Finally, the closure returns the result of the original function.

### Examples of Decorators with Closures:

Let's see a practical example of using closures with decorators to measure the execution time of a function:

```python
import time

def measure_time(func):
    def inner(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"Execution time: {execution_time} seconds")
        return result
    return inner

@measure_time
def some_function():
    # Function code here
    time.sleep(2)  # Simulate some time-consuming task

some_function()  # Output: Execution time: 2.0003676414489746 seconds
```

In this example, the `measure_time()` decorator measures the execution time of the decorated function. The `inner()` closure is responsible for starting the timer before calling the original function (`func`), ending the timer after the function finishes, calculating the execution time, and printing it. The `@measure_time` syntax applies the decorator to the `some_function()` function, and when `some_function()` is called, it prints the execution time.

Decorators with closures provide a powerful mechanism to add reusable and customizable behavior to functions without modifying their original code. They can be used for various purposes such as logging, timing, input validation, and more.&#x20;

## Advantages and Disadvantages of Closures&#x20;

Advantages of Closures:

1. Encapsulation: Closures allow you to encapsulate data and functionality within a function, making it private and inaccessible from outside the function. This provides better data protection and security.
2. Code Reusability: Closures are a powerful tool for creating reusable code. You can create a closure once and use it multiple times across your codebase without duplicating code.
3. Stateful Functions: Closures enable you to create functions that have memory of previous calls, which makes them useful for stateful applications such as web servers and GUI applications.
4. Decorators: Closures provide a powerful mechanism for creating decorators, which allow you to modify the behavior of functions without modifying their source code.

Disadvantages of Closures:

1. Memory Management: Closures hold a reference to their enclosing scope, which means that any variables or objects referenced in the closure are not garbage collected until the closure is no longer referenced. This can cause memory leaks and increased memory usage.
2. Complexity: Closures can be complex and difficult to understand, especially for novice programmers. Nested functions and nonlocal variables can be confusing and require careful attention to detail.
3. Performance Overhead: Closures can have a performance overhead, especially if they are used extensively in a codebase. The extra function calls and variable lookups can slow down the execution of the code.

<br>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://python-codelivly.gitbook.io/python-mastery-from-beginner-to-expert/advance-python/closures.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
