Scope, Lifetime, & The LEGB Rule
Scope determines where a variable is visible and accessible within your code. Lifetime determines how long that variable exists in memory. Failing to understand these rules leads to the most common bugs in software: name collisions and NameError exceptions.
1. The LEGB Resolution Rule
Section titled “1. The LEGB Resolution Rule”When you reference a variable name, Python searches for it in four specific locations, in this exact order:
- Local: Inside the current function (including parameters).
- Enclosing: Inside any “outer” functions (if using nested functions).
- Global: At the top level of your script or module.
- Built-in: Python’s pre-defined names (e.g.,
len,int,print).
If Python reaches the “B” and still can’t find the name, it raises a NameError.
2. Modifying Outer Scopes: global and nonlocal
Section titled “2. Modifying Outer Scopes: global and nonlocal”By default, functions can read outer variables but cannot modify them. If you try to assign a value to a global name, Python simply creates a new local variable with that same name (Shadowing).
The global Keyword
Section titled “The global Keyword”Used to modify a variable defined at the top level of the module.
count = 0def increment(): global count count += 1The nonlocal Keyword
Section titled “The nonlocal Keyword”Used in nested functions to modify a variable in the enclosing function’s scope.
def outer(): x = "Initial" def inner(): nonlocal x x = "Modified" inner() print(x) # Output: Modified3. Under the Hood: Frame Objects & The Stack
Section titled “3. Under the Hood: Frame Objects & The Stack”How does Python actually manage these scopes? Every time you call a function, Python creates a Frame Object on the Call Stack.
- Creation: The frame holds the function’s local variables (the “Namespace”).
- Execution: Python’s PVM executes the bytecode within that frame.
- Destruction: When the function returns, the frame is “popped” off the stack. All local variables in that frame are deleted (decremented in reference count).
Persistent Scope (Closures)
Section titled “Persistent Scope (Closures)”If an inner function is returned from an outer function, the inner function “captures” the enclosing frame. This is a Closure, and it’s why the inner function can still access outer variables even after the outer function has finished executing.
4. Shadowing: The Hidden Bug
Section titled “4. Shadowing: The Hidden Bug”Shadowing occurs when you name a local variable the same as a global variable or a built-in function.
len = 10 # Shadowing the built-in len() function
def calc(): x = 5 # Shadowing? No, just a local.
# print(len([1, 2])) # TypeError: 'int' object is not callable5. Summary Table: Scope Characteristics
Section titled “5. Summary Table: Scope Characteristics”| Scope | Defined Where? | Lifetime | Access Level |
|---|---|---|---|
| Local | Inside a function. | Duration of the call. | Read/Write (local). |
| Enclosing | Inside outer function. | Until inner function is deleted. | Read (Write with nonlocal). |
| Global | Top level of .py file. | Duration of the program. | Read (Write with global). |
| Built-in | Python Interpreter. | Always available. | Read-Only. |