Python闭包底层实现机制深度问询:基于字节码指令的原理剖析
Great question—let’s dive deep into how Python handles closures under the hood, using your code and bytecode snippets as our guide.
First, let's recap your example code for context:
def closure_test(): x = 1 def closure(): nonlocal x x = 2 print(x) return closure closure_test()()
1. How does Python capture outer function local variables?
When Python compiles closure_test, it detects that the nested function closure references the outer local variable x (the nonlocal declaration makes this explicit, but even a read-only reference would trigger the same logic). Instead of storing x in closure_test's regular local variable stack, Python wraps x in a cell object.
Look at the key bytecode lines from closure_test:
2 0 LOAD_CONST 1 (1) 2 STORE_DEREF 0 (x)
Instead of the standard STORE_FAST used for regular locals, we see STORE_DEREF—this tells us x is being stored in a cell object, not the function's local stack frame.
Next, when creating the nested closure function, these critical instructions handle the capture:
3 4 LOAD_CLOSURE 0 (x) 6 BUILD_TUPLE 1 8 LOAD_CONST 2 (<code object closure at 0x7f14ac3b9500, file "<string>", line 3>) 10 LOAD_CONST 3 ('closure_test.<locals>.closure') 12 MAKE_FUNCTION 8 (closure)
LOAD_CLOSURE: Pushes the cell object forxonto the stackBUILD_TUPLE: Packages all captured cell objects into a tuple (here, just one forx)MAKE_FUNCTION: Creates the new function object, passing the cell tuple as its closure environment (the8flag indicates the function carries a closure)
In short: Python identifies when a nested function references outer variables, wraps those variables in cells, and passes references to those cells to the nested function.
2. Where are captured variables stored?
Captured variables (the cell objects) are bound directly to the closure function. You can inspect this via the function's __closure__ attribute:
func = closure_test() print(func.__closure__) # Output looks like (<cell at 0x...: int object at 0x...>,) print(func.__closure__[0].cell_contents) # Initially 1, becomes 2 after calling func()
Each cell has a cell_contents attribute that holds the variable's actual value. The cell's lifecycle is tied to the closure function—even after closure_test finishes executing and its stack frame is destroyed, the cell persists as long as the closure exists. This is why closures can retain and modify state after the outer function exits.
3. How does the closure access these variables?
Looking at the closure function's bytecode:
5 0 LOAD_CONST 1 (2) 2 STORE_DEREF 0 (x) 6 4 LOAD_GLOBAL 0 (print) 6 LOAD_DEREF 0 (x) 8 CALL_FUNCTION 1
We use two closure-specific instructions here:
LOAD_DEREF: Fetches the cell object at index 0 from the closure's cell tuple, then reads itscell_contentsvalueSTORE_DEREF: Finds the same cell object and writes the new value to itscell_contents
Unlike LOAD_FAST/STORE_FAST (used for regular locals), these instructions are purpose-built to interact with the closure's cell-based environment.
Quick summary of the full flow
- When compiling the outer function, Python wraps referenced local variables in cell objects, using
STORE_DEREFto store values - When creating the closure,
LOAD_CLOSUREandBUILD_TUPLEpackage cells into a tuple, which is passed to the new function viaMAKE_FUNCTION - The closure holds a reference to this cell tuple in its
__closure__attribute - When the closure runs,
LOAD_DEREFandSTORE_DEREFread/write thecell_contentsof the appropriate cell, enabling access and modification of the captured variable
This design also lets multiple closures share the same cell object (e.g., if the outer function returns multiple nested functions referencing the same variable), which is how closures can share state with each other.
内容的提问来源于stack exchange,提问作者akm

