5

I was wondering, is there a function in Python that returns a dict object that contains nonlocal variables used in enclosing functions? Like vars() or locals() for local variables or globals() for global ones.

Update: As thebjorn noted, nonlocal variables that are actually used in the nested function are included in the local list. On 3.2.3 the following code

>>> def func1():
...     x=33
...     def func2():
...             # Without the next line prints {}
...             print(x)
...             print(locals())
...     func2()
... 
>>> func1()

returns {'x': 33}.

ComposM
  • 53
  • 6
  • A quick test suggests that `nonlocal` names are included in `locals` in Python 3.3, what version are you using? –  Mar 16 '14 at 14:39
  • No, they're only included in locals if they're used locally... – thebjorn Mar 16 '14 at 14:44
  • @delnan I'm using 3.2.3. In this version `locals()` returns `{}` if used inside a nested function. – ComposM Mar 16 '14 at 14:55
  • Please show the program that you used to determine this, because unless ideone.com is lying to me, it *does* work in 3.2.3 too. I suspect your test is flawed. –  Mar 16 '14 at 14:59
  • 3
    `locals()` only returns locals and _free_ variables. If you have a non-local that isn't free in your inner function (i.e. you're not using it), then `locals()` will not list it. – thebjorn Mar 16 '14 at 15:01
  • If you add `print(x)` inside `func2`, then locals will return it.. – thebjorn Mar 16 '14 at 15:02
  • @delnan I updated the question, appending the code of the function. Maybe I made a mistake somewhere? – ComposM Mar 16 '14 at 15:04

2 Answers2

3

There is no builtin nonlocals(), but you can create one yourself:

def nonlocals():
    import inspect
    stack = inspect.stack()
    if len(stack) < 3: return {}
    f = stack[2][0]
    res = {}
    while f.f_back:
        res.update({k:v for k,v in f.f_locals.items() if k not in res})
        f = f.f_back
    return res

if i run it on your program I get:

{'func2': <function func1.<locals>.func2 at 0x0000000002A03510>, 'x': 33}
thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • This won't work in IronPython without the command line option `-X:FullFrames`, which will hurt performance and should only be used for debugging. In general, an implementation of Python doesn't have to support stack frame inspection. – Eryk Sun Mar 16 '14 at 16:38
  • 2
    **DO NOT USE THIS**, it is very wrong!! It looks up variables using dynamic scoping rather than lexical scoping!! – user541686 Sep 25 '18 at 19:22
3

The accepted answer is very, very wrong—f_back gives the caller, not the lexically parent scope!

Python is lexically scoped, not dynamically scoped!

What you want can be done using the method described here:

def nonlocals():
    import inspect
    caller = inspect.currentframe().f_back
    return {k: v for k, v in caller.f_locals.items() if k in caller.f_code.co_freevars}
user541686
  • 205,094
  • 128
  • 528
  • 886
  • ehm.. aren't you using `.f_back` above (the linked answer doesn't do this). – thebjorn Sep 25 '18 at 20:19
  • 1
    @thebjorn: Yes, that is fine. The linked answer doesn't need to do this because its caller has already called `inspect.currentframe()` and provided its starting stack frame, so the answer doesn't need to do that. However, over here I don't have the starting `frame` provided to me, so I need to obtain it myself by inspecting the stack. This is *entirely separate* from the variable lookup mechanism. What I'm *not* doing here is using the `.f_back` chain to look up the variables dynamically (see the `.f_back` inside the loop in the accepted answer), which is the problem with that answer. – user541686 Sep 25 '18 at 20:21
  • Ah, I see. Can you solve the original problem too, i.e. get a dict of all lexically scoped variables that are not in `globals()` and not in `locals()`? Meaning, return `{'x':33}` from the OPs code without needing to pull the variable into locals by using it (here by calling print) or declaring it with a nonlocal statement? – thebjorn Sep 25 '18 at 20:39
  • @thebjorn: Unfortunately as far as I know it's not possible, because the enclosed function doesn't store a reference to its enclosing function. It only stores references to the cells containing the closure variables directly, which only occurs for variables that are in a closure. In fact, if the variables aren't in closures, then they may not even exist at this point. (Put another way, this would require a Python program to store its own execution history indefinitely, never discarding it because someone may later decide to look up variables from some dead frame.) – user541686 Sep 25 '18 at 21:24