Chapter 3 — Scoping: Exercise Solutions Guide
How to Use This Page
1. Attempt every exercise question on your own first — use your Samacheer Kalvi textbook and class notes. 2. Compare your answers with the solutions below only after you have written your own. 3. Read the explanations for MCQs and True/False items — understanding why an answer is correct is more important than the answer itself. 4. Practise the short and detailed answers by writing them in your own words; do not memorise verbatim. 5. Use the Exam Checklist at the end for a quick pre-exam revision round.
Key Concepts (Quick Recap)
- Scope is the region of a program where a variable name is accessible.
- Local scope: Variables defined inside a function; accessible only within that function.
- Global scope: Variables defined outside all functions; accessible throughout the module.
- Enclosing scope: Variables in the outer function of nested functions; accessible to inner functions.
- Built-in scope: Python's predefined names like
len, print, max, min, int, str. - The LEGB rule defines name resolution order: Local → Enclosing → Global → Built-in.
- The
global keyword allows a function to modify a global variable. - The
nonlocal keyword allows a nested function to modify a variable from its enclosing function. - A namespace is a container that maps names to objects; Python has built-in, global, enclosing, and local namespaces.
- Shadowing occurs when a local variable has the same name as a global variable; the local one takes precedence inside the function.
- A closure is a nested function that remembers variables from its enclosing scope even after the outer function has finished.
- Functions can access (read) variables from outer scopes, but modifying them requires
global or nonlocal.
I. Choose the Best Answer (MCQs)
1. What is the output of the following code?
x = 10
def func():
x = 20
print(x)
func()
print(x)
Answer
(a) 20 20 (b) 10 10 (c) 20 10 (d) Error
Answer(c) 20 10 Inside func(), x = 20 creates a new local variable that shadows the global x. The print(x) inside the function prints the local x (20). The print(x) outside prints the global x (10), which was never modified.
2. Which keyword is used to modify a global variable inside a function? (a) nonlocal (b) global (c) static (d) extern
Answer(b) global The global keyword tells Python that a variable inside a function refers to the global variable, allowing it to be modified. Without global, assignment inside a function creates a local variable.
3. According to the LEGB rule, what is the order in which Python searches for a variable name? (a) Local → Global → Enclosing → Built-in (b) Global → Local → Enclosing → Built-in (c) Local → Enclosing → Global → Built-in (d) Built-in → Global → Enclosing → Local
Answer(c) Local → Enclosing → Global → Built-in LEGB stands for Local, Enclosing, Global, Built-in. Python searches for a name in this order, from the innermost scope outward.
4. What is the output of the following code?
x = 5
def outer():
x = 10
def inner():
nonlocal x
x = 20
inner()
print(x)
outer()
Answer
(a) 5 (b) 10 (c) 20 (d) Error
Answer(c) 20 Inside inner(), nonlocal x refers to x in the enclosing outer() function. So x = 20 modifies outer's x from 10 to 20. The print(x) inside outer() then prints 20.
5. Which of the following is a built-in name in Python? (a) my_variable (b) len (c) outer (d) x
Answer(b) len len is a built-in function in Python, part of the built-in namespace. my_variable, outer, and x are user-defined names, not built-in.
6. What happens if a local variable and a global variable have the same name? (a) Python raises an error (b) The global variable is used inside the function (c) The local variable shadows the global variable inside the function (d) Both variables are merged
Answer(c) The local variable shadows the global variable inside the function When a function defines a local variable with the same name as a global variable, the local variable takes precedence (shadows the global) inside that function. The global variable remains unchanged and accessible outside the function.
7. What is the output of the following code?
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x)
inner()
outer()
Answer
(a) global (b) enclosing (c) local (d) Error
Answer(c) local inner() has its own local x = "local". According to LEGB, Python finds x in the Local scope first, so it prints "local" without checking Enclosing or Global scopes.
8. What is a namespace in Python? (a) A variable name (b) A container that maps names to objects (c) A Python module (d) A function definition
Answer(b) A container that maps names to objects A namespace is a system that maps names (identifiers) to objects. Python has multiple namespaces: built-in (contains len, print, etc.), global (module-level names), enclosing (outer function names), and local (current function names).
9. Which keyword is used to modify a variable from the enclosing scope in a nested function? (a) global (b) nonlocal (c) enclosing (d) outer
Answer(b) nonlocal The nonlocal keyword allows a nested function to modify a variable defined in its enclosing (outer) function. global is used for module-level variables, not enclosing-scope variables.
10. What is the output of the following code?
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c = counter()
print(c())
print(c())
Answer
(a) 1 1 (b) 0 1 (c) 1 2 (d) Error
Answer(c) 1 2 counter() returns the increment function, which forms a closure over count. The first c() call increments count from 0 to 1 and returns 1. The second c() call increments count from 1 to 2 and returns 2. The count variable persists between calls because of the closure.
II. Fill in the Blanks
1. The region of a program where a variable is accessible is called its scope.
2. The LEGB rule stands for Local, Enclosing, Global, Built-in.
3. A variable defined inside a function is called a local variable.
4. A variable defined outside all functions is called a global variable.
5. The global keyword is used to modify a global variable inside a function.
6. The nonlocal keyword is used to modify an enclosing-scope variable inside a nested function.
7. When a local variable has the same name as a global variable, the local variable shadows the global variable inside the function.
8. A namespace is a container that maps names to objects.
9. A function that remembers variables from its enclosing scope even after the outer function has finished is called a closure.
10. Python's built-in functions like len(), print(), and max() belong to the built-in scope.
III. True or False
1. A local variable can be accessed outside the function in which it is defined. False. A local variable exists only within the function where it is defined. Attempting to access it outside causes a NameError.
2. The global keyword is needed to read a global variable inside a function. False. Reading a global variable inside a function does not require the global keyword. The global keyword is only needed when you want to modify (assign to) a global variable.
3. The LEGB rule determines the order in which Python searches for a variable name. True. Python searches for a name in the order: Local, Enclosing, Global, Built-in (LEGB). It returns the first match found.
4. A closure can access variables from its enclosing scope even after the outer function has returned. True. A closure "remembers" the variables from its enclosing scope. When the inner function is called later, it still has access to those variables, even though the outer function has finished executing.
5. The nonlocal keyword is used to modify a global variable. False. nonlocal is used to modify a variable in the enclosing (outer) function scope. To modify a global variable, use the global keyword.
6. Two functions can have local variables with the same name without conflict. True. Each function has its own local namespace. Two functions can each have a local variable called x, and they are completely independent — no conflict.
7. Python creates a new local namespace every time a function is called. True. Each function call creates a new local namespace. This is why recursive calls each have their own set of local variables.
8. The del keyword can be used to remove a variable from the global scope. True. del removes a name from the current namespace. For example, del x removes the global variable x from the module's namespace.
9. If a variable is not found in local, enclosing, or global scopes, Python raises a NameError. True. If Python searches through Local, Enclosing, and Global scopes and does not find the name, and it is not a built-in, a NameError is raised.
10. The built-in namespace is created when the Python interpreter starts. True. The built-in namespace is created when the Python interpreter starts and contains all built-in functions and exceptions (like len, print, ValueError, etc.). It is the outermost scope.
IV. Match the Following
Set A
| A | B |
|---|
| 1. Local scope | c. Variables inside a function |
| 2. Global scope | e. Variables at the module level |
| 3. Enclosing scope | a. Variables in the outer function of nested functions |
| 4. Built-in scope | d. Python's pre-defined names like len, print |
5. global keyword | b. Allows modifying a global variable inside a function |
Set B
| A | B |
|---|
1. nonlocal | c. Modifies an enclosing-scope variable |
| 2. Closure | e. Remembers enclosing-scope variables |
| 3. Shadowing | a. Local variable hides a global of the same name |
| 4. Namespace | d. Container mapping names to objects |
| 5. LEGB | b. Order: Local → Enclosing → Global → Built-in |
V. Short Answer Questions
1. What is scope? Explain the different types of scope in Python.
Answer
Scope refers to the region of a program where a variable name is accessible. Python has four types of scope (LEGB rule):
1. Local (L): Variables defined inside the current function. Accessible only within that function.
def func():
x = 10 # Local variable
print(x) # 10
2. Enclosing (E): Variables in the outer function of nested functions. Accessible to inner functions.
def outer():
y = 20 # Enclosing variable
def inner():
print(y) # 20 (accesses enclosing scope)
inner()
3. Global (G): Variables defined at the module level (outside all functions). Accessible throughout the module.
z = 30 # Global variable
def func():
print(z) # 30
4. Built-in (B): Python's pre-defined names like len, print, max, int, str. Accessible everywhere.
print(len("hello")) # 5 (len is built-in)
Python searches for a name in LEGB order: Local → Enclosing → Global → Built-in.
2. Explain the LEGB rule with an example.
Answer
The LEGB rule determines the order in which Python searches for a variable name: Local → Enclosing → Global → Built-in.
# Built-in scope
len # Built-in function
# Global scope
x = "global"
def outer():
# Enclosing scope
x = "enclosing"
def inner():
# Local scope
x = "local"
print(x) # Finds 'x' in Local scope → "local"
inner()
outer()
If we remove the local x:
x = "global"
def outer():
x = "enclosing"
def inner():
# No local x defined
print(x) # Finds 'x' in Enclosing scope → "enclosing"
inner()
outer()
If we remove the enclosing x:
x = "global"
def outer():
# No enclosing x defined
def inner():
# No local x defined
print(x) # Finds 'x' in Global scope → "global"
inner()
outer()
If we remove the global x:
def outer():
def inner():
# No local, enclosing, or global x
print(len) # Finds 'len' in Built-in scope → <built-in function len>
inner()
outer()
3. What is the global keyword? Give an example.
Answer
The global keyword is used inside a function to declare that a variable refers to a module-level (global) variable, allowing the function to modify it.
Without global — creates a local variable (shadowing):
count = 0
def increment():
count = 10 # Creates a new local variable, does NOT modify global
print("Inside:", count) # 10
increment()
print("Outside:", count) # 0 (global unchanged)
With global — modifies the global variable:
count = 0
def increment():
global count # Declares that 'count' refers to the global variable
count = count + 1
print("Inside:", count)
increment() # Inside: 1
increment() # Inside: 2
print("Outside:", count) # 2 (global modified)
Key rules:
- Use
global only when you need to modify a global variable inside a function. - Reading a global variable does not require
global. - Overusing
global makes code harder to debug; prefer passing parameters and returning values.
4. What is the nonlocal keyword? How is it different from global?
Answer
The nonlocal keyword is used inside a nested function to declare that a variable refers to a variable in the enclosing (outer) function scope, allowing the inner function to modify it.
Example:
def outer():
x = 10
def inner():
nonlocal x # Refers to x in outer()
x = 20 # Modifies outer's x
print("Inner:", x) # 20
inner()
print("Outer:", x) # 20 (modified by inner)
outer()
Difference between global and nonlocal:
| Feature | global | nonlocal |
|---|
| Scope targeted | Module-level (global) variable | Enclosing function's variable |
| Use case | Modify a global variable inside a function | Modify an enclosing variable in a nested function |
| Level | Top-level scope | One level up in nested functions |
| Example | global count to modify module's count | nonlocal x to modify outer function's x |
x = "global"
def outer():
x = "enclosing"
def inner():
global x # Refers to the module-level x
x = "modified global"
# nonlocal x would refer to outer's x instead
inner()
outer()
print(x) # "modified global"
5. What is a closure? Give an example.
Answer
A closure is a nested function that remembers and can access variables from its enclosing scope even after the outer function has finished executing.
Key characteristics:
- The inner function is returned by the outer function (not called inside it).
- The inner function references variables from the enclosing scope.
- The enclosed variables persist as long as the closure exists.
Example — Counter using closure:
def make_counter():
count = 0 # Enclosing variable
def increment():
nonlocal count # Modify enclosing variable
count += 1
return count
return increment # Return the inner function (not call it)
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
Even though make_counter() has finished executing, increment() still has access to count because it forms a closure over count.
Example — Multiplier using closure:
def make_multiplier(factor):
def multiply(number):
return number * factor # 'factor' from enclosing scope
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
Each call to make_multiplier() creates a new closure with its own factor. double remembers factor=2, and triple remembers factor=3.
6. What is a namespace? What are the different types of namespaces in Python?
Answer
A namespace is a container that maps names (identifiers) to objects. It ensures that names are unique and can be managed without conflict.
Types of namespaces in Python:
1. Built-in namespace: Contains Python's built-in names (functions, exceptions, etc.). Created when the interpreter starts.
- Examples:
len, print, max, ValueError, True
2. Global namespace: Contains names defined at the module level. Created when the module is imported or executed.
- Examples: module-level variables, function definitions, class definitions
3. Enclosing namespace: Contains names in the outer function(s) of nested functions. Created when the outer function is called.
- Examples: variables defined in
outer() accessible to inner()
4. Local namespace: Contains names defined inside the current function. Created each time the function is called and destroyed when it returns.
- Examples: parameters, local variables
# Built-in namespace: len, print, int, etc.
x = "global" # Global namespace
def outer():
y = "enclosing" # Enclosing namespace
def inner():
z = "local" # Local namespace
print(len([1,2,3])) # Built-in namespace
inner()
outer()
Lifetime of namespaces:
- Built-in: exists as long as the interpreter runs.
- Global: exists as long as the module is loaded.
- Enclosing/Local: exists only during function execution.
VI. Detailed Answer Questions
1. Explain the LEGB rule in detail with examples showing how Python resolves variable names at each level.
Answer
The LEGB rule defines the order in which Python searches for a variable name when it is referenced. Python checks each scope from innermost to outermost:
L — Local: Inside the current function. E — Enclosing: In the enclosing function(s) for nested functions. G — Global: At the module (top-level) scope. B — Built-in: Python's pre-defined names.
Comprehensive example:
# Built-in scope
str = "built-in would be here, but we shadow it"
# Global scope
message = "I am global"
def outer():
# Enclosing scope
message = "I am enclosing"
def inner():
# Local scope
message = "I am local"
print(message) # Local: "I am local"
inner()
print(message) # Enclosing: "I am enclosing"
outer()
print(message) # Global: "I am global"
Removing local — Python looks to enclosing:
message = "I am global"
def outer():
message = "I am enclosing"
def inner():
# No local 'message' defined
print(message) # Enclosing: "I am enclosing"
inner()
outer()
Removing enclosing — Python looks to global:
message = "I am global"
def outer():
# No enclosing 'message'
def inner():
# No local 'message'
print(message) # Global: "I am global"
inner()
outer()
Removing global — Python looks to built-in:
def outer():
def inner():
print(len) # Built-in: <built-in function len>
inner()
outer()
Important: Assignment creates a local variable (by default)
x = 10
def func():
print(x) # Reading: OK — finds global x
func()
# But:
x = 10
def func():
print(x) # UnboundLocalError!
x = 20 # Assignment makes x local; print(x) is before assignment
func()
# Python sees 'x = 20' and treats x as local for the ENTIRE function.
# print(x) comes before assignment, so x is unbound → UnboundLocalError
SolutionUse global or nonlocal to modify outer variables.
2. Explain local, global, and enclosing scopes with examples. Show how global and nonlocal keywords modify variables in outer scopes.
Answer
Local scope: Variables defined inside a function are local. They exist only during the function call and are destroyed when the function returns.
def greet():
name = "Kavi" # Local variable
print(f"Hello, {name}")
greet() # Hello, Kavi
# print(name) # NameError: name 'name' is not defined
Global scope: Variables defined outside all functions are global. They can be read inside any function, but modifying them requires global.
counter = 0 # Global variable
def show():
print(counter) # Reading global: OK → 0
show()
Modifying global with global:
counter = 0
def increment():
global counter # Declare counter as global
counter += 1
increment()
increment()
print(counter) # 2
Enclosing scope (nested functions): Variables in an outer function are accessible to the inner function. Modifying them requires nonlocal.
def outer():
x = "enclosing"
def inner():
print(x) # Reads enclosing x → "enclosing"
inner()
outer()
Modifying enclosing scope with nonlocal:
def outer():
count = 0
def inner():
nonlocal count # Declare count as nonlocal
count += 1
return count
inner()
inner()
print(count) # 2 (modified by inner)
outer()
Summary table:
| Keyword | Scope targeted | When to use |
|---|
global | Module-level variable | To modify a global variable inside a function |
nonlocal | Enclosing function's variable | To modify an enclosing variable in a nested function |
Combined example:
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print("1. Local:", x) # "local"
inner()
print("2. Enclosing:", x) # "enclosing"
outer()
print("3. Global:", x) # "global"
3. Explain closures in Python with multiple examples. Why are closures useful?
Answer
A closure is a nested function that retains access to variables from its enclosing scope even after the outer function has finished executing.
Conditions for a closure: 1. There must be a nested function (function inside a function). 2. The inner function must refer to a variable from the enclosing scope. 3. The outer function must return the inner function (not call it).
Example 1 — Simple closure:
def outer(message):
def inner():
print(message) # 'message' from enclosing scope
return inner # Return inner function (not call it)
hi = outer("Hello!")
hi() # Hello! — inner still remembers message="Hello!"
bye = outer("Goodbye!")
bye() # Goodbye! — a separate closure with message="Goodbye!"
Example 2 — Counter closure:
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c1 = make_counter()
print(c1()) # 1
print(c1()) # 2
print(c1()) # 3
c2 = make_counter() # New closure with its own count
print(c2()) # 1
print(c2()) # 2
Each call to make_counter() creates a new closure with its own count. c1 and c2 are independent counters.
Example 3 — Power function using closure:
def make_power(exp):
def power(base):
return base ** exp # 'exp' from enclosing scope
return power
square = make_power(2)
cube = make_power(3)
print(square(5)) # 25 (5 ** 2)
print(cube(3)) # 27 (3 ** 3)
Why closures are useful: 1. Data hiding: The enclosed variable is not directly accessible from outside — it can only be accessed through the closure. 2. State retention: Closures remember values between calls without using global variables. 3. Function factories: Closures can create specialised functions (like square and cube from make_power). 4. Avoiding global variables: Closures provide a clean way to maintain state without polluting the global namespace. 5. Callback functions: Closures are commonly used in event handlers and asynchronous programming.
4. Explain Python namespaces in detail. How do they relate to scope?
Answer
A namespace is a mapping from names to objects. It ensures that names are unique and prevents naming conflicts.
Types of namespaces:
| Namespace | Created when | Destroyed when | Contains |
|---|
| Built-in | Interpreter starts | Interpreter stops | len, print, max, int, True |
| Global | Module is imported/executed | Module is unloaded | Module-level variables, functions, classes |
| Enclosing | Outer function is called | Outer function returns | Variables in outer function |
| Local | Function is called | Function returns | Parameters, local variables |
Example showing all namespaces:
# Built-in namespace: len, print, max, etc.
# Global namespace
PI = 3.14159 # Global variable
def circle_area(): # Global name
pass
def outer(enclosed_param): # Global name 'outer'
# Enclosing namespace
enclosed_var = 10
def inner(local_param): # Enclosing name 'inner'
# Local namespace
local_var = enclosed_param + enclosed_var
result = max(local_var, 5) # 'max' from built-in
print(result) # 'print' from built-in
return result
return inner
func = outer(20)
print(func(5)) # 25
How namespaces relate to scope:
- Scope is the textual area of a program where a namespace is directly accessible.
- When you reference a name, Python searches namespaces in LEGB order.
- Each function call creates a new local namespace.
- The
globals() function returns the current global namespace as a dictionary. - The
locals() function returns the current local namespace as a dictionary.
Demonstrating globals() and locals():
x = 10
def func(a, b):
c = a + b
print("Local namespace:", locals())
return c
func(3, 5)
# Local namespace: {'a': 3, 'b': 5, 'c': 8}
print("Global 'x' in globals:", 'x' in globals()) # True
5. Explain shadowing in Python. What problems does it cause and how can they be avoided?
Answer
Shadowing occurs when a variable in an inner scope has the same name as a variable in an outer scope. The inner variable "shadows" (hides) the outer one within its scope.
Example of shadowing:
x = 100 # Global
def func():
x = 200 # Local variable shadows global x
print(x) # 200 (local x)
func()
print(x) # 100 (global x is unaffected)
Common shadowing scenarios:
1. Local shadows global:
name = "Global"
def greet():
name = "Local" # Shadows global 'name'
print(name) # "Local"
greet()
print(name) # "Global"
2. Shadowing built-in names (dangerous):
# NEVER do this:
len = 50 # Shadows the built-in len() function!
# print(len([1, 2, 3])) # TypeError: 'int' object is not callable
3. Local shadows enclosing:
def outer():
x = "enclosing"
def inner():
x = "local" # Shadows enclosing 'x'
print(x) # "local"
inner()
print(x) # "enclosing" (unchanged)
outer()
Problems caused by shadowing:
- Confusion: It is unclear which variable is being referenced.
- Bugs: Unintended shadowing can lead to hard-to-find errors.
- Shadowing built-ins: Accidentally shadowing
len, list, str, etc. can break code. - UnboundLocalError: Assigning to a variable anywhere in a function makes it local for the entire function, even before the assignment.
x = 10
def func():
print(x) # UnboundLocalError!
x = 20 # This makes x local for the ENTIRE function
func()
How to avoid shadowing problems: 1. Use distinct, descriptive variable names instead of generic names like x, y, tmp. 2. Never name variables after built-in functions (len, list, str, dict, max, min). 3. Use global or nonlocal when you intentionally want to modify an outer variable. 4. Keep functions small and focused to reduce the chance of accidental shadowing. 5. Use linters (like pylint) to detect shadowing warnings.
Common Mistakes
1. Forgetting global when modifying a global variable: Assigning to a variable inside a function creates a local variable by default. Use global to modify a global variable.
2. Forgetting nonlocal when modifying an enclosing variable: Same issue as global, but for nested functions. Use nonlocal to modify an enclosing variable.
3. UnboundLocalError from assignment after reference: If you assign to a variable anywhere in a function, Python treats it as local for the entire function. Referencing it before the assignment causes UnboundLocalError.
4. Shadowing built-in names: Naming a variable list, str, len, or dict shadows the built-in. This can cause TypeError when you later try to use the built-in.
5. Confusing global and nonlocal: Use global for module-level variables and nonlocal for enclosing-function variables. Using the wrong keyword causes a SyntaxError.
6. Expecting closures to share state across instances: Each call to the outer function creates a new closure with its own enclosed variables. Two closures from the same outer function do not share state.
7. Assuming local variables persist between function calls: Local variables are destroyed when the function returns. Use closures or global variables if you need state persistence.
Exam Checklist
- [ ] Can define scope and explain the four types (Local, Enclosing, Global, Built-in)
- [ ] Can explain the LEGB rule and trace variable resolution through each level
- [ ] Can explain the difference between local and global variables
- [ ] Can use the
global keyword to modify a global variable inside a function - [ ] Can use the
nonlocal keyword to modify an enclosing variable in a nested function - [ ] Can define namespace and list its types
- [ ] Can explain closures with examples (counter, multiplier, power factory)
- [ ] Can explain shadowing and its problems
- [ ] Can predict output of code involving scope,
global, and nonlocal - [ ] Know that assignment in a function makes the variable local for the entire function