Defining local variable with exec

The code

x=1
def a():
    exec("x=2")
    print(x)
a()

outputs 1.
But the code

x=1
def a():
    x=2
    print(x)
a()

outputs 2. Should not

exec("[ANY STRING]")

always have the same effect as

[ANY STRING]

in code? I am not sure if this is bug or intended this way.

This is working as intended as you didn’t pass anything into your exec() that would cause it to work outside of itself. In other words exec() knows nothing about namespaces, so you have to pass that sort of stuff in (run help(exec) for more details).

1 Like

Brett, your answer is incomplete and misleading in three ways.

It’s not true that exec without a namespace argument doesn’t modify the
local environment. It behaves as Olle7 expects in the global and class
scopes:

x = 2
exec("x = 1")
print(x)  # -> prints 1, not 2

class K:
    x = 2
    exec("x = 999")
    print(x)  # -> prints 999

With no additional arguments, the exec function defaults to
operating in globals() and locals().

Secondly, it also works as Olle7 expects inside functions in Python 2.

And thirdly, even if we explicitly pass locals() to exec, it still
won’t work inside a function in Python3, at least not in CPython. (Other
Python implementations may differ.)

Olle7, the behaviour you see is intentional, it is an optimisation to
help functions run faster. It is not a language feature, but a
limitation of CPython that the local namespace inside a function is
special. Both eval and exec can read variables but exec may not be able
to write variables inside a function. There are notes about this in the
docs:

https://docs.python.org/3/library/functions.html#locals

https://docs.python.org/3/library/functions.html#exec

I seem to remember the docs explicitly mentioning that this was an
implementation detail, not a language feature, but I can’t find that
now and don’t have time to go looking.

In any case, from time to time people suggest changing the behaviour to
operate as expected, but such proposals haven’t gone anywhere as yet.

2 Likes

Sorry about the mistake.

No worries Brett its an obscure corner of behaviour of a function which
is rarely used.

I now understand execs behavior when it is called in function better.

g1="G1 BEFORE"
g2="G2 BEFORE"
def a():
    exec("x=1.78438")
    try:
        print("x:", x)
    except NameError:
        print("x is not reachable.")
        print("local variables:",locals())
        print("x in dict returned by function locals:",locals()["x"])
        print("x was not reachable on line 4 althought it is in locals, beacuse it was added to locals in exec in function, which causes undefined behaivor.")
        exec("y=x+1")
        if locals()["y"]==2.78438:
            print("x must be reachable in another exec.")
            eval("print('x and y are also reachable in eval. x:',x,'; y:',y)")
            exec("global g1\ng1='G1 AFTER'")
            if "g1" in globals() and "g1" not in locals():
                print("gloabal variable g1:",g1)
                global g2
                exec("g2='G2 AFTER'")
                if "g2" in locals():
                    print("local variable g2:",g2)
                    print("g2 was not treated as global variable, because it is decleared global outside of exec")
                    exec("del x,y")
                    print("local variables: ",locals())
                    print("global variables: ", globals())
a()
print("there is also a global varable g2:",g2)

program goes to all if’s.