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.
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):
...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With