Understanding Closures in Python: A Complete Guide
Supporting classes as part of its built-in object-oriented programming (OOP) paradigm, Python is a high-level general-purpose language. Sometimes, when working with Python’s variables, you might wish to hide a variable without creating an additional class to maintainable the code. Furthermore, you could wish to add little functionality to a function without generating another unnecessary function. Under such circumstances, decorators for function modification and closures for variable hiding can be applied as fixes.
Closures:
Though not in memory, a closure is a function object that remembers values in the enclosing scope. A closure results when a nested function makes reference to variables from its enclosing scope. Although the enclosing scope is no longer active, the closure “closes over” the variables it refers so retaining their values. Data encapsulation and state preservation between function calls are commonly accomplished via closures.
Decorators:
A decorator is a means of altering the behavior of a function or a class without thus altering its source code. It lets one wrap an existing function or class with another function or class thereby augmenting its capabilities. Projects including logging, timing, input validation, authentication, and more often call for decorators. They offer a way to prioritize issues and maintain the code modular and reusable.
A Basic Closure Example
Simply said, even after the outer function has completed, a closure is a tool for remembering and accessing the values of variables from their surrounding environment.
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
closure = outer_function(5)
print(closure(3)) # Output: 8
In the given example, the “outer_function” defines an inner function called “inner_function” using an input “x,” which accepts another argument “y,” therefore producing the sum of “x” and “y.”
The inner function “inner_function” then is returned by the “outer_function.” Being a nested function, “inner_function” has access to the variables in the enclosing scope of “outer_function,” in which case “x” is used.
Why and how might one use closures?
Data hiding in Python is the use of closures — that is, the definition of a variable inside an outer function and subsequently application in an inner function. This helps to capture the data and stops outside of the function modification from occurring.
class SecureData:
def __init__(self, data):
self.data = data
self.password = 'secret'
def get_data(self, passwd):
if passwd == self.password:
return self.data
else:
return None
secure_data = SecureData('my sensitive data')
print(secure_data.get_data('secret')) # Output: 'my sensitive data'
print(secure_data.get_data('wrong password')) # Output: None
Two instance variables — data and password — are first established in the __init__ function of the SecureData class.
Defined within the SecureData class, the get_data function accepts a password as a parameter. It relates the given password with the class’s password instance variable. Should they fit, it returns the data instance variable. Otherwise, it comes back None.
An instance of the SecureData class is generated (secure_data) to get the safe data. After that, passing the password as a parameter, you can execute the get_data function on the secure_data instance.
def create_secure_data(data):
password = 'secret'
def get_data(passwd):
if passwd == password:
return data
else:
return None
return get_data
secure_data = create_secure_data('my sensitive data')
# Now, the 'secure_data' variable contains a reference to
# the inner function 'get_data' which can access the 'password'
# and 'data' variables of the outer function 'create_secure_data'.
# To retrieve the secure data, you need to call the 'secure_data'
# function with the correct password.
print(secure_data('secret')) # Output: 'my sensitive data'
print(secure_data('wrong password'))
What Are Decorators
A decorator is a higher-order function that takes another function as an argument, adds some functionality to it, and generates a new function devoid of source code modification. Decorators encapsulate objects inside another function, therefore changing their behavior either individually or generally. Here’s an illustration:
def decorator_function(func):
def wrapper_function():
print("Before function is called.")
func()
print("After function is called.")
return wrapper_function
@decorator_function
def hello():
print("Hello, world!")
hello()
Investigating Decorators in-depth
Decorators in Python allow meta-programming, a kind of programming whereby the code may interact with other codes at either run-time or compile-times. Decorators, then, offer a mechanism to change the behavior of functions or classes without changing their source code, hence illustrating a metaprogramming technique.
def debug(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with args: {args}, \
kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"{func.__name__} returned: {result}")
return result
return wrapper
@debug
def add(a, b):
return a + b
result = add(3, 5)
print(result)
Conclusions
Python closures and decorators enable you to create strong and flexible code. Closures recall values from their outer scope even whilst running. Decorators allow you change to classes or functions without affecting their source code. Combine closures with decorators to create even more magic, hence enhancing the reusability and functionality of your work.