How does a function become a loadable cell variable?

I am trying to figure out how an inner function, that is a constant, becomes a cell variable. Furthermore, how does it get looked up?

Consider my example:

>>> def outer():
...    def inner(x):
...       if x <= 0:
...          return 0
...       else:
...          return x + inner(x-1)
...    return inner(3)
...
>>>
>>> outer()
6
>>> outer.__code__.co_cellvars
('inner',)
>>> outer.__code__.co_consts
(None, <code object inner at 0x0000022FBC03E2F0, file "<stdin>", line 2>, 'outer.<locals>.inner', 3)
>>> outer.__code__.co_consts[1].co_freevars
('inner',)

This is a common pattern for me for recursive functions. There is an outer function that sets up some initial conditions for an inner function that does the actual recursion.

Presumably while generating bytecode, the Python runtime is able to walk up the potential frames and find that recursing inner() references a constant contained in outer().

I was thinking maybe outer turns it in a local, but I was sabotaged:

>>> def outer():
...    print(locals())
...    def inner(x):
...       if x <= 0:
...          return 0
...       else:
...          return x + inner(x-1)
...    return inner(3)
...
>>>
>>> outer()
{}
6

I’m trying to mimic the proper behavior in a personal Python interpreter project and I can’t figure out all the plumbing. Like, I detect the name “inner” and I find it in the constants for “outer” but I can’t tell what the right thing to do is with that information–especially when I get to actually running the LOAD_DEREF it takes to find inner() at run time.

Your inner function doesn’t show up in locals because it doesn’t exist
yet. def is an executable statement, not a declaration, so it doesn’t
run until execution reaches the statement:

def outer():
    print("Before", locals())
    def inner():
        pass
    print("After", locals())

And the output is:

Before {}
After {'inner': <function outer.<locals>.inner at 0x7fca232a5510>}

Adam wrote:

“I’m trying to mimic the proper behavior in a personal Python
interpreter project and I can’t figure out all the plumbing. Like, I
detect the name “inner” and I find it in the constants for “outer” but I
can’t tell what the right thing to do is with that information
–especially when I get to actually running the LOAD_DEREF it takes to
find inner() at run time.”

If you just call the outer() function, it will know how to find inner()
at run time and call it. (But I guess you know that.) So I’m afraid it
isn’t really clear to me what you are trying to do. Why, and how, are
you running the LOAD_DEREF bytecode by hand?

Are you trying to write your own Python interpreter? Or doing some
bytecode hacking?

Perhaps if you tell us more about what you are trying to do, we may be
able to help you better.

Thanks Steven. This was actually something I was missing. I don’t set my functions as locals in the MAKE_FUNCTION opcode.

Are you trying to write your own Python interpreter? Or doing some
bytecode hacking?

I’m writing my own interpreter. I was trying to avoid writing a ton about it because then I get into tl;dr territory. On the other hand, when I write too little about context, I get into the x-instead-of-y territory haha.