Where is nonlocals()?

1.3k Views Asked by At

How do I obtain the non-local variables for the current scope? The functions vars, locals, and globals exist, but is there a function to get the nonlocals?

Why aren't the nonlocals listed when calling vars?

Update

My issue is that there's no way to enumerate the variables available in the current scope, as neither vars or globals includes the non-locals AFAICT.

I frequently use vars in code such as the following:

'{meh[0]}/{meh[3]} {a}{b}{c}'.format(**vars())

Which fails if any of these variables are in the scope of a containing function.

1

There are 1 best solutions below

2
On

From within running code, you can easily get the names of the nonlocal variables - but retriving their content in a way a call to locals gets you a dictionary is a bit trickier.

The used nonlocal variable names are stored in the current running code object, in the co_freevars attribute.

So, getting the nonlocal names is a matter of:

names = inspect.currentframe().f_code.co_freevars

The contents for these variables, however, are stored in the __closure__ attribute (func_closure, in Python 2), of the function object instead. (Not the code object). The problem is that, without "aid from outside", there is no easy way for a running code to get to the function object it is running on. You can get to the frame object, which links to the code object, but there are no links back to the function object. (For a top level defined function one could explicitly use the function known name, as used in the def statement` but for an enclosed function, that is returned to a caller, there is no way of knowing its name either).

So, one has to resort to a trick - getting all the objects that link to the current code object, by using the gc module (garbage collector) - there is a gc.get_referrers call - it will return all the function objects that link to the code object one holds.

So, inside a function with non_local variables one could do:

import inspect, gc

from types import FunctionType

def a(b):
    b1 = 2
    def c():
        nonlocal b1
        print (b)
        code =  inspect.currentframe().f_code  
        names = code.co_freevars
        function = [func for func in gc.get_referrers(code) if isinstance(func, FunctionType)][0]
        nonlocals = dict (zip(names, (x.cell_contents for x in function.__closure__ )))
        print(nonlocals)
        return inspect.currentframe()
    return c

c = a(5)
f = c()

And therefore retrieve the names and values of the nonlocals. But this won't work if you have more than one instance of that function around (that is, if the function of interested was created more than once with more than one call to the function that generates it) - because all of those instances will link to the same code object. The example above, assumes there is only one function running with the current code - and would work correctly in this case. Another call to the factrory function would create another function, with potentially other values for the nonlocal variables, but with the same code object - the function = list genrator above would retrieve all of those, and arbitrarily pick the first of those.

The "correct" function is the one on which the current code is executing - I am trying to think of a way of retrieving this information, but can't get to it. If I can, I will complete this answer, but for now, this can't help you to retrieve the nonlocals values values.

update However, just using "eval" with a nonlocal variable name will work. It just has to be either explicitly declared as nonlocal, or otherwise hardcoded by name in the nested function, so that the closure is actually created.

def blah():
    b = 1
    def bleh():
        nonlocals = sys._getframe().f_code.co_freevars
        print(f"{eval(nonlocals[0])=}")
        b  # b has to be "touched" so the closure is created.
    return ble

Previously to finding out that a simple "eval" could work, I was digressing thus:

It looks like that the only thing linking the current running frame to the function object where the nonlocal variables values are held is created at run time inside the native side of the Python interpreter. I can't think of a way of getting to it short of using the ctypes module to look at interpreters data structures at runtime, which would, of course, be unsuitable for any actual production code.

The bottom line: you can reliably retrieve the nonlocal variable names. But it looks like you can't get their value given their name as a string (nor rebind then).

You could try opening a feature-request for a "nonlocals" call on Python's bug tracker or on Python-ideas mailing list.