5

The following code works in Python 2.7, to dynamically inject local variables into a function scope:

myvars = {"var": 123}

def func():
    exec("")
    locals().update(myvars)
    print(var)

func()
# assert "var" not in globals()

It's a bit subtle, but the presence of an exec statement indicates to the compiler that the local namespace may be modified. In the reference implementation, it will transform the lookup of the name "locals" from a LOAD_GLOBAL op into a LOAD_NAME op, enabling addition to the local namespace.

In Python 3, exec became a function instead of a statement, and the locals() call returns a copy of the namespace, in which modifications have no effect.

How can you recreate the idea in Python 3, to dynamically create local variables inside a function? Or is this a "feature" only possible in Python 2.x?

Note: The code must not bind the names in outer scopes.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 1
    Note that `help(locals)` in Python3 includes the text "Whether or not updates to this dictionary will affect name lookups in the local scope and vice-versa is \*implementation dependent\* and not covered by any backwards compatibility guarantees." – Adam Smith Aug 21 '19 at 15:58
  • 2
    @AdamSmith Thanks for pointing that out. Answers which rely on CPython implementation details are welcome. – wim Aug 21 '19 at 15:59
  • https://stackoverflow.com/questions/37600997/python-locals-update-not-working – user1889297 Aug 21 '19 at 16:09

1 Answers1

1

I wouldn't recommend it, but you could put the body of the function in a class statement:

myvars = {"var": 123}

def func():
    class Bleh:
        locals().update(myvars)
        print(var)

func()

As with the Python 2 code, the fact that modifying locals() works in this case is technically undocumented and subject to change. A class scope must use an actual dict for local variable resolution, but theoretically, a later Python implementation might copy that dict for locals() or something like that.

As with the Python 2 code, doing this will interfere with closure variable resolution, including subtle cases like the following:

myvars = {"var": 123}

def func():
    class Bleh:
        locals().update(myvars)
        print([var for x in (1, 2, 3)])

func()

or even

def func():
    class Bleh:
        var = 123
        print([var for x in (1, 2, 3)])

func()

Both of these cases result in a NameError, because the list comprehension creates a new scope that doesn't have access to var.

In Python 2, trying to use closure variables with exec would lead to a SyntaxError. If you try to replicate the functionality in Python 3 with a class statement, you don't get any such detection.

Finally, Python will try to build a class after the class statement is done, which could cause weird issues with things like __set_name__ methods running unexpectedly. You could fix this issue, at least, by using a metaclass:

class NoClass(type):
    def __new__(self, *args, **kwargs):
        return None

def func():
    class Bleh(metaclass=NoClass):
        ...
user2357112
  • 260,549
  • 28
  • 431
  • 505