Closures and Decorators

Closures

1. Explain free variables.
Free variables are nonlocal variables.
2. Describe closures (What does it contain?).
Closure is a function plus an extended scope that contains the free variables.
3. How to create a closure?
Closures are created when a function is defined inside another function and it has access to a free (nonlocal) variable.
4. What does closure allows a function to do intuitively?
A closure allows a function to “remember” the environment in which it was created.
5. How to call the closure?
To call a closure, you invoke the inner function that was returned by the outer function.
6. When a function finished running, the local scope will be eliminated. How does a closure retains the enclosing variables that are nonlocal to the closure? Provide an illustration.

Closures remember the non-local variables even after the outer function has finished executing because the inner function keeps a reference to the namespace of the outer function in which it was created.

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)  # Creates a closure over 'x'

# The outer function has finished running, but the closure still retains a reference to 'x'
result = closure(5)  # Accesses 'x' from the captured environment
print(result)  # Output: 15
7. What does attribute fn.__closure__ tells us?
The fn.__closure__ attribute is used to access the tuple of cell objects that contain bindings for the non-local variables referenced by a function. In simple words, it tells us the non-local / free variables in a function.
8. One of the closure’s use-case is data encapsulation and information hiding. Elaborate this specific use-case.
Closures allow you to create private variables within a function, achieving a level of information hiding. The inner function can access and modify the variables in the outer function, but they are not directly accessible from outside the closure.
9. One of the closure’s use-case is develop callback functions. Elaborate this specific use-case.
Closures are often used in callback functions, where a function is passed as an argument to another function. The closure retains access to the variables from its defining scope.

Decorators

1. Explain decorator’s use case.
Decorators allow us to extend the behaviour of a function, without modifying the source code.
2. Provide the basic structure of a decorator in code.
def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        # Code to be executed before the original function
        result = original_function(*args, **kwargs)
        # Code to be executed after the original function
        return result
    return wrapper_function
3. How to apply a decorator to a function?

Use the @decorator_function syntax. For example,

@decorator_function
def my_function():
    print("Hello, from the original function!")

my_function()

@decorator_function is essentially the same as my_function= decoration_function(my_function)

4. List the common use-cases of decorators.
  • Logging information.
  • Measuring execution time.
  • Memoization (to cache results).
  • Synchronisation.
5. Explain how do decorator work? (Hint: How does decorators take in functions?)
In decorators, the original functions are taken as the argument into another function and the original function is called inside the wrapper function that will extend the functionalities.
Last updated on 23 Aug 2024