Allow variable name specified only for stack frame object

I am trying to figure out if it is possible to allow variables in the frames of the stack (as returned 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

As an example, consider the following code snippet:

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 viewed through attributes f_localsand f_globalsa 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 is closure variables. As far as I know, they are not stored in the dictionary, like local and global variables. To make matters worse, variables only become trailing variables if the function actually accesses them (for example, reading its value as _ = closure_varor 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()

resolve_variable ? ?

+4
1

, . Python , .

>>> import inspect
>>> class Demo(object):
...     def __del__(self):
...         print("Too late, it gone.")
... 
>>> def f():
...     a = Demo()
...     def g():
...         return inspect.currentframe()
...     return g
... 
>>> frame = f()()
Too late, it gone.

, a frame. .

, , f_locals. , , , :

>>> def f():
...     a = 1
...     class Foo(object):
...         print(a)
...         print(inspect.currentframe().f_locals)
...     return Foo
... 
>>> f()
1
{'__module__': '__main__', '__qualname__': 'f.<locals>.Foo'}
<class '__main__.f.<locals>.Foo'>

CPython ( , , LOAD_CLASSDEREF inspect.getclosurevars), , ctypes, gc.get_referents, .

, , f_locals dict , ; frame.f_locals , .

+5

Source: https://habr.com/ru/post/1695786/


All Articles