简体   繁体   中英

I'm trying to understand Python's exec() function

So I have a string which is a function like

code = """def somefn(x):
    return x + x
    y = somefn(z)"""

and I am trying to run this in another function like

def otherfn(codestr):
    z = 2
    exec(codestr)
    return y

otherfn(code)

but it gives me the error:

Traceback (most recent call last): File "C:/Users/Admin/Desktop/heh.py", line 11, in otherfn(code) File "C:/Users/Admin/Desktop/heh.py", line 9, in otherfn return y NameError: name 'y' is not defined

it works fine outside the function like

z=2 exec(codestr) print(y)

it finds y just fine but not sure why it is bugging out when it is in the function.

How can I fix this? Is it something to do with globals() and locals()? Using Python 3.6 btw.

There are several problems with your code. First of all, you have an indentation problem - the y gets 'defined' inside the somefn() function, after the return so it never actually gets the chance to get onto the stack. You need to redefine your code to :

code = """def somefn(x):
    return x + x
y = somefn(z)"""

But that's just the tip of the iceberg. The greater issue is that exec() cannot modify the local scope of a function. This is due the fact that Python doesn't use a dict for lookup of variables in the local scope so all changes from the exec() do not get reflected back to the stack to enable lookup. This causes a weird issue where exec() seemingly changes the locals() dictionary, but Python still throws a NameError :

def otherfn(codestr):
    z = 2
    exec(codestr)
    print(locals()["y"])  # prints 4
    return y  # NameError

otherfn(code)

This is an intended behavior, as explained in the issue4831 , and further pontificated in the official docs :

Note : The default locals act as described for function locals() below: modifications to the default locals dictionary should not be attempted. Pass an explicit locals dictionary if you need to see effects of the code on locals after function exec() returns.

But if you have to reflect the changes you can just do a post-exec local scope update:

def otherfn(codestr):
    z = 2
    locals_ = locals()
    exec(codestr, globals(), locals_)
    y = locals_["y"]
    return y

otherfn(code)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM