Setting variables with exec inside a function

30,300

Solution 1

You're almost there. You're trying to modify a global variable so you have to add the global statement:

old_string = "didn't work"
new_string = "worked"

def function():
    exec("global old_string; old_string = new_string")
    print(old_string)

function()

If you run the following version, you'll see what happened in your version:

old_string = "didn't work"
new_string = "worked"

def function():
    _locals = locals()
    exec("old_string = new_string", globals(), _locals)
    print(old_string)
    print(_locals)

function()

output:

didn't work
{'old_string': 'worked'}

The way you ran it, you ended up trying to modify the function's local variables in exec, which is basically undefined behavior. See the warning in the exec 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.

and the related warning on locals():

Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

Solution 2

As an alternative way of having exec update your global variables from inside a function is to pass globals() into it.

>>> def function(command):
...    exec(command, globals())
...
>>> x = 1
>>> function('x += 1')
>>> print(x)
2

Unlike locals(), updating the globals() dictionary is expected always to update the corresponding global variable, and vice versa.

Share:
30,300

Related videos on Youtube

Spacenaut
Author by

Spacenaut

Updated on July 09, 2022

Comments

  • Spacenaut
    Spacenaut almost 2 years

    I just started self teaching Python, and I need a little help with this script:

    old_string = "didnt work"   
    new_string = "worked"
    
    def function():
        exec("old_string = new_string")     
        print(old_string) 
    
    function()
    

    I want to get it so old_string = "worked".

    • Daniel Roseman
      Daniel Roseman about 10 years
      What's the point of this? Why do you want to use exec here?
    • atelcikti1
      atelcikti1 about 10 years
      "I just started self teaching Python.." sounds like they're is just trying to wrap their head around a new language...
  • SarcasticSully
    SarcasticSully almost 7 years
    Is there a way to modify it in the scope of the calling function?
  • atelcikti1
    atelcikti1 almost 7 years
    @SarcasticSully there is no calling function here.. If you wonder about something tangentially associated with the question, or answer, you should ask a new question (with the proper context, code samples, etc.) Leaving new questions in comments of 3 year old answers isn't likely to be helpful for anyone...
  • Martijn Pieters
    Martijn Pieters over 5 years
    Rather than add a global statement, you can simply set the namespaces: exec('old_string = new_string', globals(), globals()).
  • atelcikti1
    atelcikti1 over 5 years
    @MartijnPieters if you do that (exec('..', globals(), globals())) any variable created in the exec becomes a global -- probably not what you want?
  • atelcikti1
    atelcikti1 over 5 years
    Try function("[t for t in range(5)]"), then print(t)... Maybe too many unintended consequences?
  • Martijn Pieters
    Martijn Pieters over 5 years
    Yes, and that could be the goal. Most people forget about the ability to specify the namespaces (including new dictionaries), if you don’t want all names as globals using a custom namespace and then extracting what you need afterwards might be the better option anyway.
  • atelcikti1
    atelcikti1 over 5 years
    @MartijnPieters that's starting to sound like an answer ;-) While I agree with your argument in general (modulo Python leaking loop variables), I stand by my answer in this particular context for this particular user. (I've upvoted khelwood's answer since it's a useful insight).
  • atelcikti1
    atelcikti1 over 5 years
    @MartijnPieters Ah, I see the question has been heavily edited.. Check the history for context.
  • khelwood
    khelwood over 5 years
    @thebjorn I get NameError: name 't' is not defined, which is what I would expect. Are you using an old version of Python before they fixed the variables-escaping-comprehensions bug?
  • atelcikti1
    atelcikti1 over 5 years
    Yes 2.7 all the way! (sigh). Did they fix the for-loop variable escape too?
  • khelwood
    khelwood over 5 years
    No, if I understand you correctly. I think that's intended behaviour.