50

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".

thebjorn
  • 26,297
  • 11
  • 96
  • 138
Spacenaut
  • 549
  • 1
  • 4
  • 4

2 Answers2

44

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.

thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • 2
    Is there a way to modify it in the scope of the calling function? – SarcasticSully Jun 17 '17 at 06:09
  • @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... – thebjorn Jun 17 '17 at 15:08
  • 2
    Rather than add a `global` statement, you can simply set the namespaces: `exec('old_string = new_string', globals(), globals())`. – Martijn Pieters Aug 24 '18 at 21:52
  • @MartijnPieters if you do that (`exec('..', globals(), globals())`) any variable created in the exec becomes a global -- probably not what you want? – thebjorn Aug 25 '18 at 11:05
  • 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. – Martijn Pieters Aug 25 '18 at 11:08
  • @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). – thebjorn Aug 25 '18 at 11:15
  • @MartijnPieters Ah, I see the question has been heavily edited.. Check the history for context. – thebjorn Aug 25 '18 at 11:18
10

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 variables.

khelwood
  • 55,782
  • 14
  • 81
  • 108
  • Try `function("[t for t in range(5)]")`, then `print(t)`... Maybe too many unintended consequences? – thebjorn Aug 25 '18 at 11:07
  • @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? – khelwood Aug 25 '18 at 12:30
  • Yes 2.7 all the way! (*sigh*). Did they fix the for-loop variable escape too? – thebjorn Aug 25 '18 at 14:39
  • No, if I understand you correctly. I think that's intended behaviour. – khelwood Aug 25 '18 at 16:16