Python Functions and Scope Questions and Answers — Questions and Answers
Question 1: A developer writes a function to log messages, intending for each call to start with a fresh list of tags unless specified. However, they notice that tags from previous calls are persisting. What is the most likely cause of this bug? ```python def log_message(message, tags=[]): tags.append('INFO') print(f"{message}: {tags}") log_message("System start") log_message("User login") ```
- The `tags` list was not declared as a global variable.
- The function is a closure, causing it to retain the state of `tags`.
- A mutable object (a list) is used as a default argument, which is evaluated only once when the function is defined. (Correct answer)
- The `append` method modifies the list in-place, which is not allowed for function parameters.
Correct answer: A mutable object (a list) is used as a default argument, which is evaluated only once when the function is defined.
In Python, default function arguments are evaluated once at the time the function is defined, not each time it is called. When a mutable object like a list or dictionary is used as a default argument, all calls to the function that don't provide a value for that argument will share the *same* object. In this case, the list `tags` is created once, and each subsequent call to `log_message` appends to the same list, leading to the accumulation of tags.
Question 2: In Python's scope resolution, what is the LEGB rule?
- The order of operations for arithmetic expressions: Logarithm, Exponentiation, Globals, Brackets.
- The sequence Python follows to find a variable: Local, Enclosing, Global, Built-in. (Correct answer)
- A rule for naming variables: Lowercase, Extended, Grouped, Brief.
- A memory management principle: Least-used, Expired, Garbage-collected, Blocked.
Correct answer: The sequence Python follows to find a variable: Local, Enclosing, Global, Built-in.
The LEGB rule dictates the order in which Python searches for a name (variable, function, etc.). It checks scopes in the following order: 1. **L**ocal: The current function's scope. 2. **E**nclosing: The scope of any enclosing functions (in nested functions). 3. **G**lobal: The top-level module scope. 4. **B**uilt-in: The scope containing Python's built-in names like `print()` and `len()`.
Question 3: What is the primary purpose of the `nonlocal` keyword in Python?
- To declare a variable that can be accessed from any module in the program.
- To prevent a variable from being modified by any function.
- To allow an inner function to modify a variable from its nearest enclosing (but non-global) scope. (Correct answer)
- To create a variable that exists only within a `for` or `while` loop.
Correct answer: To allow an inner function to modify a variable from its nearest enclosing (but non-global) scope.
The `nonlocal` keyword is used inside nested functions. It indicates that a variable is not local to the inner function but belongs to the nearest enclosing function's scope. This allows the inner function to rebind or modify that variable directly, rather than creating a new local variable with the same name.
Question 4: Consider the following code. What will be the output? ```python def multiplier_factory(n): def multiplier(x): return x * n return multiplier double = multiplier_factory(2) triple = multiplier_factory(3) print(double(5), triple(5)) ```
- 10 10
- An error because `n` is not defined in the `multiplier` scope.
- 10 15 (Correct answer)
- 2 3
Correct answer: 10 15
This code demonstrates a closure. The `multiplier_factory` function returns another function, `multiplier`. The returned `multiplier` function "remembers" the value of `n` from the environment where it was created. When `multiplier_factory(2)` is called, it creates a function that remembers `n=2`. When `multiplier_factory(3)` is called, it creates a different function that remembers `n=3`. Therefore, `double(5)` returns 10 (5*2) and `triple(5)` returns 15 (5*3).
Question 5: A programmer needs to modify a global variable from within a function. Which of the following code snippets correctly accomplishes this?
- ```python count = 0 def increment(): count += 1 ```
- ```python count = 0 def increment(): global count count += 1 ``` (Correct answer)
- ```python count = 0 def increment(count): count += 1 return count ```
- ```python count = 0 def increment(): nonlocal count count += 1 ```
Correct answer: ```python count = 0 def increment(): global count count += 1 ```
To modify a variable in the global scope from within a function, you must explicitly declare your intent using the `global` keyword. Without it, Python would treat `count` as a new local variable within the `increment` function and raise an `UnboundLocalError` because it's being referenced before assignment. The `nonlocal` keyword is for modifying variables in an enclosing scope, not the global scope.
Question 6: Which of the following statements about function arguments in Python is true?
- All arguments are passed by value, meaning a copy of the object is passed to the function.
- Arguments are passed by reference, and any modification to a parameter within a function will always affect the original object.
- Python uses a mechanism called 'pass-by-object-reference' or 'pass-by-assignment', where the function gets a copy of the reference to the object. (Correct answer)
- Positional arguments must always be specified after keyword arguments in a function call.
Correct answer: Python uses a mechanism called 'pass-by-object-reference' or 'pass-by-assignment', where the function gets a copy of the reference to the object.
Python's argument passing mechanism is often described as 'pass-by-object-reference' or 'pass-by-assignment'. When you pass an argument, the function parameter becomes a new reference to the same object. If the object is mutable (like a list), changes made to it inside the function will affect the original object. If the object is immutable (like a number or string), reassigning the parameter inside the function creates a new local object, leaving the original unchanged. This behavior is distinct from pure pass-by-value or pass-by-reference.
A developer writes a function to log messages, intending for each call to start with a fresh list of tags unless specified.
However, they notice that tags from previous calls are persisting.
What is the most likely cause of this bug?
```python
def log_message(message, tags=[]):
tags.append('INFO')
print(f"{message}: {tags}")
log_message("System start")
log_message("User login")
```