Disassembling a Nested Function

Posted by Afsal on 11-Oct-2024

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.