Understanding Python Closures: Concepts and Practical Examples
Introduction to Python closures Functions
Closures in Python are a powerful feature that allows a function to retain access to its enclosing scope's variables even after the outer function has finished execution. Closures are used to create functions with some pre-configured behavior, making them particularly useful in decorators and callback functions. This tutorial will focus on examples to help you understand how closures work with maximum practical code.
Example 1: Basic Closure Example
This example demonstrates a basic closure, where an inner function retains access to variables from its enclosing function even after the outer function has completed.
Code:
def outer_function(msg):
# Outer function that takes a message as an argument
def inner_function():
# Inner function that references the outer function's variable
print(msg)
return inner_function # Return the inner function
# Create a closure
closure = outer_function("Hello, World!")
# Call the closure
closure() # Output: Hello, World!
Explanation:
- Outer Function: 'outer_function()' defines a local variable 'msg' and an inner function 'inner_function()' that accesses msg.
- Returning the Inner Function: The outer function returns 'inner_function()' without executing it.
- Closure: When we call 'closure()', it still has access to 'msg', even though 'outer_function()' has finished executing.
Example 2: Closure with a Counter
This example shows how closures can maintain state between function calls, demonstrated by a simple counter that remembers its value.
Code:
def make_counter():
# Outer function that initializes a counter
count = 0
def increment():
# Inner function that increments the counter
nonlocal count
count += 1
return count
return increment # Return the inner function
# Create a counter closure
counter = make_counter()
# Call the counter multiple times
print(counter()) # Output: 1
print(counter()) # Output: 2
print(counter()) # Output: 3
Explanation:
- Outer Function: 'make_counter()' initializes a variable count and defines an inner function 'increment()' that modifies count.
- 'nonlocal' Keyword: 'nonlocal' is used to modify the count variable in the enclosing scope.
- Closure: The counter closure maintains its own count state across multiple calls.
Example 3: Customizing Functions with Closures
This example demonstrates how closures can create customized functions, such as a multiplier, that carry specific behavior based on the outer function's arguments.
Code:
def multiplier(factor):
# Outer function that takes a factor
def multiply(number):
# Inner function that multiplies a number by the factor
return number * factor
return multiply # Return the inner function
# Create closures with different factors
double = multiplier(2)
triple = multiplier(3)
# Use the closures
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
Explanation:
- Outer Function: 'multiplier()' takes a 'factor' and defines an inner function 'multiply()' that multiplies a given number by this factor.
- Closures: The 'double' and 'triple' closures are customized functions that multiply numbers by 2 and 3, respectively.
Example 4: Closures as Decorators
This example illustrates how closures can be used as decorators, adding additional functionality (like logging) to existing functions without modifying their code.
Code:
def logger(func):
# Outer function that takes a function as an argument
def log_wrapper(*args, **kwargs):
# Inner function that logs the function call and then executes it
print(f"Calling {func.__name__} with arguments: {args}, {kwargs}")
return func(*args, **kwargs)
return log_wrapper # Return the inner function
# Applying the closure as a decorator
@logger
def add(x, y):
return x + y
# Call the decorated function
print(add(3, 4)) # Output: Calling add with arguments: (3, 4), {}
# 7
Explanation:
- Outer Function: 'logger()' takes a function as an argument and defines an inner function 'log_wrapper()' that adds logging behavior.
- Decorator: The 'log_wrapper()' closure logs the call details and then calls the original function.
- Using the Closure: The 'add()' function is decorated with the 'logger' closure, so it logs its arguments whenever it is called.
Example 5: Closures for Data Encapsulation
This example demonstrates using closures for data encapsulation, creating an account with operations that securely manage the balance without exposing it directly.
Code:
def account(initial_balance):
# Outer function that initializes balance
balance = initial_balance
def get_balance():
# Inner function to get the balance
return balance
def deposit(amount):
# Inner function to deposit money
nonlocal balance
balance += amount
return balance
def withdraw(amount):
# Inner function to withdraw money
nonlocal balance
if amount <= balance:
balance -= amount
return balance
else:
return "Insufficient funds"
# Return a dictionary of functions for encapsulated operations
return {"get_balance": get_balance, "deposit": deposit, "withdraw": withdraw}
# Create an account closure
my_account = account(100)
# Perform operations
print(my_account["get_balance"]()) # Output: 100
print(my_account["deposit"](150)) # Output: 250
print(my_account["withdraw"](200)) # Output: 50
Explanation:
- Data Encapsulation: The 'account()' function encapsulates the balance and provides controlled access via closures.
- Operations: The closures ('get_balance', 'deposit', 'withdraw') manipulate and access the balance securely without exposing it directly.
- Using the Closures: Operations on the account are performed using the returned closures, ensuring data encapsulation.
It will be nice if you may share this link in any developer community or anywhere else, from where other developers may find this content. Thanks.
https://w3resource.com/python/python-closures-with-examples.php
- Weekly Trends and Language Statistics
- Weekly Trends and Language Statistics