# Python Closures ### Table of Contents - [Introduction](#introduction) - [Functions in Python](#functions-in-python) - [Nested Functions](#nested-functions) - [Understanding Closures](#understanding-closures) - [Characteristics of Python Closures](#characteristics-of-python-closures) - [Examples of Python Closures](#examples-of-python-closures) - [When to Use Closures](#when-to-use-closures) - [Limitations of Closures](#limitations-of-closures) - [Closures vs Objects](#closures-vs-objects) - [Conclusion](#conclusion) --- ### Introduction A **closure** in Python is a function object that remembers values in its enclosing scope, even if that scope is no longer present. Closures are a fundamental concept that allows for more flexibility in code. In simple terms, a closure allows a function to "remember" and use variables from its environment, even when the environment in which it was created no longer exists. Closures provide a neat way to implement function factories, encapsulation, and maintain state between function calls without the need for external variables. --- ### Functions in Python In Python, functions are first-class citizens, meaning: - They can be passed around as arguments. - They can be returned from other functions. - They can be assigned to variables. Functions allow you to encapsulate logic, making code modular and reusable. ```python def greet(name): return f"Hello, {name}" print(greet("Alice")) # Output: Hello, Alice ``` ### Nested Functions Python supports defining functions inside other functions. These are known as nested functions or inner functions. Nested functions are able to access variables of the enclosing scope. ```python def outer_function(): def inner_function(): print("Inner Function") inner_function() outer_function() ``` --- ### Understanding Closures A closure occurs when an inner function references variables from the outer function’s scope, and the outer function has finished execution, but the inner function still has access to those variables. ```python def outer_function(msg): def inner_function(): print(msg) return inner_function closure_func = outer_function("Hello, World!") closure_func() # Output: Hello, World! ``` Key Point: The variable msg is retained by the inner_function even after outer_function has finished executing. This is what makes it a closure. --- ### Characteristics of Python Closures Nested Function: A closure must have a nested function. Reference to Outer Variables: The inner function must reference variables from the outer function. Return of Inner Function: The outer function must return the inner function. --- ### Examples of Python Closures **Example 1:** Basic Closure ```python def multiply_by(n): def multiplier(x): return x * n return multiplier times3 = multiply_by(3) print(times3(10)) # Output: 30 ``` **Example 2:** Counter Using Closures ```python def make_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter ``` counter1 = make_counter() print(counter1()) # Output: 1 print(counter1()) # Output: 2 --- ### When to Use Closures Closures are useful when: You want to retain state between function calls. You want to avoid using global variables. You need function factory behavior (generating functions dynamically). --- ### Limitations of Closures Memory Usage: Since closures retain references to variables from the enclosing scope, they may use more memory. Debugging: Debugging closures can sometimes be tricky because the retained values are not immediately visible. Complexity: Overuse of closures may make code harder to understand. --- ### Closures vs Objects Closures can be an alternative to objects in scenarios where you want to keep state but don’t need the full functionality of a class. Objects are more versatile, but closures provide a lightweight solution for specific problems. ```python # Using a Closure def make_multiplier(n): def multiplier(x): return x * n return multiplier multiplier3 = make_multiplier(3) print(multiplier3(5)) # Output: 15 # Using an Object class Multiplier: def __init__(self, n): self.n = n def multiply(self, x): return x * self.n multiplier_obj = Multiplier(3) print(multiplier_obj.multiply(5)) # Output: 15 ``` --- ### Conclusion Closures in Python are a powerful tool that can help manage state and make your code more modular. They enable functions to "remember" values from their environment, even after the outer function has finished execution. Understanding closures can lead to more efficient and cleaner code, especially when you want to avoid global variables or manage dynamic function behavior. # Practise on Python closures #### 1. What is a Python closure? - [ ] A function that does not return any value. - [ ] A function with default arguments. - [x] A function that retains access to variables from its enclosing scope . - [ ] A recursive function. --- ### 2. Which of the following is NOT true about closures in Python? - [ ] Closures can capture and remember values from the enclosing scope. - [x] Closures can modify the outer function’s local variables without any special keyword. - [ ] Closures are created when a nested function references variables from its outer function. - [ ] Closures help in data encapsulation. --- ### 3. What does the following code print? ```python def outer(): msg = "Hello" def inner(): print(msg) return inner func = outer() func() ``` - [ ] It raises an error. - [ ] It prints None. - [x] It prints Hello. - [ ] It prints the function address. --- ### 4. What keyword is required if the inner function in a closure wants to modify a variable from the outer function? - [ ] global - [x] nonlocal - [ ] local - [ ] No keyword is needed. --- ### 5. Closures are typically used when: - [ ] You want to return multiple values from a function. - [x] You want to retain state between function calls. - [ ] You need to handle exceptions. - [ ] You want to create global variables. --- ### 6. What is the output of the following code? ```python def make_multiplier(n): def multiplier(x): return x * n return multiplier times3 = make_multiplier(3) print(times3(5)) ``` - [ ] 5 - [ ] 3 - [ ] 10 - [x] 15 --- ### 8. In the context of closures, which keyword would you use to refer to a variable defined in the enclosing scope? - [ ] nonlocal - [ ] local - [ ] global - [ ] self --- ### 9. How can you tell if a function is a closure? - [ ] It must not have parameters. - [ ] It must return a dictionary. - [ ] It must reference variables from its outer function’s scope. - [ ] It must call another function. --- ### 10. Which of the following is a common use case for closures? - [ ] To implement recursion. - [ ] To create functions with state. - [ ] To reduce the time complexity of a program. - [ ] To improve memory management. - --- ### 11. What is a potential benefit of using closures in Python? - [ ] They provide data encapsulation. - [ ] They prevent memory leaks. - [ ] They eliminate the need for loops. - [ ] They automatically optimize the code execution. - --- ### 12. What is the output of the following code? ```python def outer_function(): msg = "Python" def inner_function(): msg = "Closures" print(msg) inner_function() outer_function() ``` - [ ] Python - [ ] It raises an error. - [ ] None - [ ] Closures --- ### 13. What is the key difference between closures and global variables in Python? - [ ] Closures use more memory than global variables. - [ ] Closures retain data from an outer function’s scope, while global variables are accessible throughout the entire program. - [ ] Global variables provide better performance than closures. - [ ] Closures can only be used inside loops. --- ### 14. What is the primary reason for using the nonlocal keyword in closures? - [ ] To create a global variable. - [ ] To modify a variable in the enclosing scope. - [ ] To prevent a variable from being accessed. - [ ] To define a constant. --- ### 15. What is the output of this code? ```python def counter(): count = 0 def increment(): nonlocal count count += 1 return count return increment counter_func = counter() print(counter_func()) print(counter_func()) ``` - [ ] 0, 1 - [ ] 1, 0 - [ ] 1, 2 - [ ] 1, 1 --- ### 16. What will the following code output? ```python def func_factory(x): def inner_func(y): return x + y return inner_func new_func = func_factory(10) print(new_func(5)) ``` - [ ] 10 - [ ] 15 - [ ] 5 - [ ] 15 --- ### 17. Can closures have more than one enclosing variable from the outer function? - [ ] No, closures can only reference one variable. - [ ] Yes, closures can reference multiple variables from the outer function. - [ ] No, closures can only reference constants. - [ ] Yes, but only if they are global variables. --- ### 18. What is the scope of the variables retained in a closure? - [ ] Global scope - [ ] Local scope - [ ] Enclosing scope - [ ] Module-level scope --- ### 19. Which of the following describes a limitation of closures? - [ ] They cannot be used in nested functions. - [ ] They might increase memory usage by retaining references to the outer function’s scope. - [ ] They cannot modify any variables from the outer function. - [ ] Closures cannot be passed as arguments. --- ### 20. How do closures compare to classes in terms of managing state? - [ ]Closures cannot manage state, but classes can. - [ ] Closures provide a lightweight alternative to classes for managing state. - [ ] Classes are always preferred over closures for managing state. - [ ] Closures and classes are identical in functionality. --- ### 21. What is the output of the following code? ```python def power_of(n): def power(x): return x ** n return power square = power_of(2) print(square(3)) ``` - [ ] 2 - [ ] 3 - [ ] 6 - [ ] 9 --- ### 22. What is the primary difference between a closure and a normal function? - [ ] A closure has no return statement. - [ ] A closure cannot take arguments. - [ ] A closure retains access to variables from the enclosing scope. - [ ] A normal function cannot be nested. --- ### 23. What is the result of executing the following code? ```python def adder(a): def add_to(b): return a + b return add_to add_five = adder(5) print(add_five(3)) ``` - [ ] 3 - [ ] 8 - [ ] 8 - [ ] 5 --- ### 24. Which of the following is NOT a characteristic of closures? - [ ] Closures automatically modify global variables. - [ ] Closures can retain values from the outer function’s scope. - [ ] Closures can access variables from the enclosing scope. - [ ] Closures allow functions to have private variables. --- ### 25. Which of the following statements is correct about closures? - [ ] Closures can capture and retain variables from the outer scope. - [ ] Closures only work with integer values. - [ ] Closures eliminate the need for recursive functions. - [ ] Closures are always faster than regular functions. --- ### 26. What is the effect of using nonlocal in closures? - [ ] It raises an error. - [ ] It allows the inner function to modify variables in the outer function’s scope. - [ ] It makes variables global. - [ ] It prevents the inner function from accessing the outer function’s variables. --- ### 27. What is the output of this code? ```python def make_greeter(greeting): def greet(name): return f"{greeting}, {name}" return greet hello_greeter = make_greeter("Hello") print(hello_greeter("Alice")) ``` - [ ] Hello, Alice - [ ] Alice, Hello - [ ] Hello - [ ] Error --- ### 28. Can a closure be passed as an argument to another function? - [ ] Yes, closures can be passed like any other function. - [ ] No, closures cannot be passed as arguments. - [ ] Yes, but only if they do not capture any variables. - [ ] No, closures are not considered functions. --- ### 29. What is the output of the following code? ```python def adder(a): def add_to(b): return a + b return add_to add_ten = adder(10) print(add_ten(7)) ``` - [ ] 10 - [ ] 17 - [ ] 7 - [ ] Error --- ### 30. What happens if a variable referenced in a closure is deleted from the outer function’s scope after the closure is created? - [ ] The closure will raise a `NameError`. - [ ] The closure will automatically update the variable to `None`. - [ ] The closure will stop working. - [ ] The closure will still retain the original value of the variable. ---