Hi Pythonistas!
We have already learned how function executes under the hood. In this post we will learn about how a nested function works under the hood.
Code
import dis
def outer(a):
def inner(b):
return b * 2
return inner(a)
dis.dis(outer)
Output
2 0 LOAD_CONST 1 (<code object inner at 0x73da047ab7e0, file "<stdin>", line 2>)
2 LOAD_CONST 2 ('outer.<locals>.inner')
4 MAKE_FUNCTION 0
6 STORE_FAST 1 (inner)
4 8 LOAD_FAST 1 (inner)
10 LOAD_FAST 0 (a)
12 CALL_FUNCTION 1
14 RETURN_VALUE
Disassembly of <code object inner at 0x73da047ab7e0, file "<stdin>", line 2>:
3 0 LOAD_FAST 0 (b)
2 LOAD_CONST 1 (2)
4 BINARY_MULTIPLY
6 RETURN_VALUE
Explanation
LOAD_CONST 1 (<code object inner at ...>): This loads the code object for the nested inner function. A code object is a low-level representation of the function’s bytecode, which is loaded into memory.
LOAD_CONST 2 ('outer.<locals>.inner'): This loads the name of the inner function as a string: 'outer.<locals>.inner'. Python stores the name of the function for debugging and introspection purposes.
MAKE_FUNCTION 0: This creates the actual function object for inner by combining the code object and the function name. The argument 0 here indicates no closure is created because inner doesn't reference any variables from outer.
STORE_FAST 1 (inner): This stores the newly created inner function in the local variable inner.
LOAD_FAST 1 (inner): This loads the inner function object onto the stack.
LOAD_FAST 0 (a): This loads the argument a passed to the outer function onto the stack.
CALL_FUNCTION 1: This calls the inner function with a as its argument. The number 1 indicates that the function takes one argument (which is a in this case).
RETURN_VALUE: The result of inner(a) is returned as the final output of outer.
Disassembly of inner
LOAD_FAST 0 (b): This loads the argument b (passed to inner) onto the stack.
LOAD_CONST 1 (2): This loads the constant 2 onto the stack.
BINARY_MULTIPLY: This performs the multiplication of b * 2.
RETURN_VALUE: This returns the result of b * 2 from the inner function.
Understanding this disassembly gives a detailed look at how Python manages nested functions, creating them dynamically at runtime and efficiently handling arguments and returns through bytecode execution.