I'm trying to find out if it's possible to resolve variables in stack frames (as returned by inspect.currentframe()).
In other words, I'm looking for a function
def resolve_variable(variable_name, frame_object):
return value_of_that_variable_in_that_stackframe
For an example, consider the following piece of code:
global_var = 'global'
def foo():
closure_var = 'closure'
def bar(param):
local_var = 'local'
frame = inspect.currentframe()
assert resolve_variable('local_var', frame) == local_var
assert resolve_variable('param', frame) == param
assert resolve_variable('closure_var', frame) == closure_var
assert resolve_variable('global_var', frame) == global_var
bar('parameter')
foo()
Local and global variables are trivially looked up through the f_locals and f_globals attributes of the frame object:
def resolve_variable(variable_name, frame_object):
try:
return frame_object.f_locals[variable_name]
except KeyError:
try:
return frame_object.f_globals[variable_name]
except KeyError:
raise NameError(varname) from None
But the problem are closure variables. They aren't stored in a dictionary like the local and global variables, as far as I know. To make things even worse, variables only become closure variables if the function actually accesses them (for example by reading its value like _ = closure_var or writing to it with nonlocal closure_var; closure_var = _). So there are actually 3 different cases:
global_var = 'global'
def foo():
unused_cvar = 'unused' # actually not a closure variable at all
readonly_cvar = 'closure'
nonlocal_cvar = 'nonlocal'
def bar(param):
nonlocal nonlocal_cvar
local_var = 'local'
_ = readonly_cvar
nonlocal_cvar = 'nonlocal'
frame = inspect.currentframe()
assert resolve_variable('local_var', frame) == local_var
assert resolve_variable('param', frame) == param
assert resolve_variable('unused_cvar', frame) == 'unused'
assert resolve_variable('readonly_cvar', frame) == readonly_cvar
assert resolve_variable('nonlocal_cvar', frame) == nonlocal_cvar
assert resolve_variable('global_var', frame) == global_var
bar('parameter')
foo()
How can I rewrite my resolve_variable function to support all of these? Is it even possible?
__closure__and change the captured values.nonlocal closure_varcapture it even if you never reference or assign to it? It did in 3.0; I don’t know if I’ve ever checked since then…barfrom within the same function it was defined in, you can cheat and look atf_back.f.localsto see all the locals offoo, captured or not. I don’t know if that’ll help for your real use case, if you have one, but it’ll work here.