11

I am trying to run this code but it seems that the exec() is not executing the string inside the function:

def abc(xyz):
    for i in fn_lst:
        s = 'temp=' + i + '(xyz)'
        exec(s)
        print (temp)

abc('avdfbafadnf')

The error I am receiving:

NameError                                 Traceback (most recent call last)
<ipython-input-23-099995c31c78> in <module>()
----> 1 abc('avdfbafadnf')

<ipython-input-21-80dc547cb34f> in abc(xyz)
      4         s = 'temp=' + i + '(word)'
      5         exec(s)
----> 6         print (temp)

NameError: name 'temp' is not defined

fn_lst is a list of function names i.e: ['has_at', 'has_num' ...]

Please let me know an alternative to exec() if possible in such a scenario.

Dimitris Fasarakis Hilliard
  • 136,212
  • 29
  • 242
  • 233
Ankit Malik
  • 128
  • 1
  • 5
  • 1
    The correct answer, is to include locals(), and/or globals() in the argument list, so that the result will be part of the scope outside of the exec() call. See my answer for an example of how to do this. – DrM Jan 15 '19 at 18:13
  • No, the correct answer is to stop using `exec` to do something that can be more easily done without it. – chepner Oct 29 '21 at 21:00
  • @chepner see my edits, just now. In fact it is pretty simple, it even can be a one liner, and I do it all the time. – DrM Oct 29 '21 at 21:11

4 Answers4

14

First we show how to make a variable set by the string passed to exec(), available outside of the call to exec(). And then we show some examples of how to make a variable available outside of a call to a function that calls exec().

The central concepts include that exec() takes as arguments, the string to be executed and two dictionaries to serve as global and local scope.

For example, we can pass the actual global and local scope, like this:

exec( 'a = 3', globals(), locals() )

print( a )

This will print the following result:

3

However, there is considerable flexibility in what dictionaries we choose to pass to exec(), and this provides a few ways to set a variable in the local scope from a function that calls exec().

For example, we can pass the current local scope to a function and then use that as the local dictionary for exec(), like this:

def demofunction( adict ):

    exec( 'a=1.', globals(), adict )

print( 'before calling the function' )
try:
    print( a )
except Exception as e:
    print( e )
    
demofunction( locals() )

print( 'after calling the function' )
print( 'a =', a )

This prints:

before calling the function
name 'a' is not defined

after calling the function
a = 1.0

Since the calling scope is global to the scope inside the function, another simple way to set a local variable from inside a function, is to just use globals() as the second argument for exec().

def demofunction( adict ):

    exec( 'a=1.', None, globals() )

print( 'before calling the function' )
try:
    print( a )
except Exception as e:
    print( e )
    
demofunction( locals() )

print( 'after calling the function' )
print( 'a =', a )

And this again, prints:

before calling the function
name 'a' is not defined

after calling the function
a = 1.0

So, we see that exec() in fact, can create variables in our local scope from inside a function.

Also, you are not limited to globals() and locals(). You can pass it any valid dictionary.

def demofunction( adict ):

    exec( 'a=1.', None, adict )


somedict = { 'b': 1 }
print( somedict )
    
demofunction( somedict )

print( somedict )

Now the output is:

{'b': 1}
{'b': 1, 'a': 1.0}

Note: In the first examples it would have been sufficient to use the local argument alone, i.e. omitting globals(). Both were included here to illustrate the more general case. You can read about "Scope" in Python, in the Python Textbook - Scope

DrM
  • 2,205
  • 12
  • 25
  • 1
    good answer, but some basic explanation would be appreciated! – JohnE Jan 15 '19 at 15:55
  • also, I believe only `globals()` is needed here, and not `locals()` – JohnE Jan 15 '19 at 16:08
  • @ShadowRanger please see the above comment, and added examples for setting vars from inside of a function using exec() – DrM Oct 29 '21 at 20:52
  • thanks, this looks like a nice explanation but it's a long time since I had made the comment so I had forgotten all about this! – JohnE Oct 31 '21 at 01:09
12

I would like to mention that many "standard" answers, previously suggested in this topic, do not work inside a function. For example, consider the following code snippet:

def test():
    exec( 'a = 3', globals(), locals() )
    print(a)
test()

Everything seems fine. However, this code gives an error in Python 3:

NameError: name 'a' is not defined

I tried some methods using the compile function suggested in other forums, but they still do not work for me (at least with the options I have seen mentioned).

According to my research, this the closest code that I have seen working:

def test():
    lcls = locals()
    exec( 'a = 3', globals(), lcls )
    a = lcls["a"]
    print(f'a is {a}')
test()

It successfully prints:

a is 3

I think this is an important topic overall. Sometimes when you work with symbolic algebra libraries, like Sympy, defining variables though the exec function can be very convenient for Scientific Computing.

I hope somebody knows a good answer to the problem.


EDIT:

Nowadays, I rarely use exec anymore. I have realized that a better/shorter solution to the OP question is to simply define local variables using the eval function. The code of the OP could have been written as:

def abc(xyz):
    for i in fn_lst:
        temp = eval(i + '(xyz)')
        print (temp)
abc('avdfbafadnf')
# problem solved :)

Since the variable name temp was already hard-coded into the function, using eval doesn't change the generality of the solution. If the name of the variable isn't known beforehand, eval can also be used as follows:

def advanced_eval(expr):
    var_names  = expr.split('=')[0].replace(' ','')
    rhs_values = eval('='.join(expr.split('=')[1:]))
    return var_names, rhs_values

If you define name,value = advanced_eval('a=3+3'), the code will effectively output that name = 'a' and value = 6.

C-3PO
  • 513
  • 6
  • 13
  • 1
    It doesn't work because `locals()` is a copy of the locals as a `dict`, not a true view of them (where modifications affect the actual locals). And the reason it can't be a view is that function locals are actually allocated at compile time to a fixed size array of locals (the names of the variables aren't even used outside of tracebacks and the like, the compiler converts to the index associated with the name). In theory, `exec` could be made to modify an existing local variable, but it definitely can't *create* a new local. – ShadowRanger Mar 27 '21 at 03:16
  • Interesting, that also explains why my code mysteriously worked. Thanks for the feedback. – C-3PO Mar 29 '21 at 03:46
  • @ShadowRanger, in fact I do create new local variables within a function using exec. The scope is simply and always whatever dictionary you pass to it. – DrM Sep 26 '21 at 16:14
  • @DrM: You can make locals into the dictionary provided. You can't create new locals in the scope of the function that invoked `exec` (and it would be an implementation detail if you could modify existing locals). The OP is trying to make a new local within `exec`, then access it outside `exec`; that doesn't work (unless you're at global scope, in which case there are no locals involved). – ShadowRanger Sep 26 '21 at 17:56
  • @ShadowRanger Of course. But you *can* pass locals() from the outside scope into your function as an argument and then use it in exec() inside the function to set those variables in the outside scope. As I recall, I have a few codes that do that. Alternatively, you can return the dictionary and load whatever you want into local scope. So there are a lot of ways to accomplish this end. – DrM Sep 26 '21 at 22:52
  • @DrM: None of it contradicts my original comment (and for the record, "locals from the outside scope" being set only works in global scope, where it's not actually locals). I'm not sure what you think needed correcting; clearly you can fill an arbitrary dictionary with the "locals" produced in `exec`. You can manually copy from arbitrary dictionaries to actual locals. But `exec` itself can't create a new *actual* local, for the exact reasons I already gave. You're responding to "You can't do X" with "You can do Y and Z", which, while true, is entirely beside the point. – ShadowRanger Sep 26 '21 at 23:15
  • 1
    This answer is the only one that works for python 3 in a web environment (apache). I tried literally all possible ways of modifying existing local & global vars but exec() failed to modify them though it was totally successful on the commandline (repl). The only way to use exec in web environment is to modify the (arbitrary) object you send as your `local` or `global` argument (local shown here): `o = {'x': None} \n exec('x = 3', None, o)\n print('now look at x: {}'.format(o['x'])`. BTW - print works here because I'm using Django in development, so using `python runserver` – Mark B Sep 27 '21 at 14:13
4

After spending so much time doing hit and trial on this problem, I can say that using exec like this was working without any problem inside function, otherwise it threw error. I have tested this for both functions and variables.

def main():
  x = "def y():\n\treturn('This will work')"
  #pass only globals() not locals()
  exec(x,globals())
  print(y())
  
main()

def test():
    #pass only globals() not locals()
    exec( 'a = 3', globals())
    print(a)
test()

Here is a screenshot of this working on W3School's online interpreter (you can copy/paste and test it here yourself) enter image description here

Tushar Gautam
  • 255
  • 4
  • 9
  • Thanks for the answer, your code indeed works. However, I noticed that the variable `a` after calling `test()` is defined globally. Thus, the code `test();print(a)` will effectively print `3` outside the function. This may cause severe bugs in certain applications, so it's something to keep in mind. – C-3PO Feb 22 '22 at 03:09
1

Instead of using exec with function names, just keep the function objects in the list:

fn_lst = [has_at, has_num, ...]

and perform the call directly:

def abc(xyz):
    for i in fn_lst:
        temp= i(xyz)
        print(temp)
Dimitris Fasarakis Hilliard
  • 136,212
  • 29
  • 242
  • 233