Question

I'm using an exec() statement to set a value, like so:

foo = 3
def return_4():
    return 4
instruction = 'foo = return_4()'
exec(instruction)                     # <---- WHERE THE MAGIC HAPPENS
print(foo)

This comes out as 4, as I expect.

My program has operations for manipulating a Rubik's cube. In this stripped down version, I'll do four things:

  1. I'll instantiate a cube, filling in one face (with abbreviations for 'front top left' and 'front bottom right' and the like).

  2. I'll have a function that rotates that front face.

  3. I'll have an 'interpreter' function, which takes a cube and a list of instructions, and applies those instructions to the cube, returning the modified cube. Here's where I use 'exec' (and where I think the breakage happens).

  4. Finally, I'll run the interpreter on my partial cube with the instruction to rotate the face one time.

+

my_cube = [['FTL', 'FTM', 'FTR',
            'FML', 'FMM', 'FMR',
            'FBL', 'FBM', 'FBR'],
            [],[],[],[],[]] # other faces specified in actual code

def rotate_front(cube):
    front = cube[0]
    new_front = [front[6],front[3],front[0],
                 front[7],front[4],front[1],
                 front[8],front[5],front[2]]
    # ...
    ret_cube = cube
    ret_cube[0] = new_front
    # pdb says we are returning a correctly rotated cube,
    # and calling this directly returns the rotated cube
    return ret_cube

def process_algorithm(cube=default_cube, algorithm=[]):
    return_cube = cube
    for instruction in algorithm:
        exec('return_cube = ' + instruction + '(return_cube)')  # <--- NO MAGIC!
        # ACCORDING TO pdb, return_cube HAS NOT BEEN ROTATED!
    return return_cube

process_algorithm(cube = my_cube, algorithm = ['rotate_front'])

If I replace the exec(x = y) formation with x = eval(y), it seems to work. return_cube = eval(instruction + '(return_cube)')

So maybe this is just academic. Why does the toy example work, and the actual code fail? (Am I doing something obvious and silly, like missing an equals sign? I'm going to kick myself, I bet...)

Thanks for any help anyone can offer.

Was it helpful?

Solution

On Python 2.x, exec was a statement that changed variable lookup from LOAD_GLOBAL and LOAD_FAST to LOAD_NAME to every name you access on your function. That means it first search the local scope to see if can find the name to after check the global scope.

Now, on Python 3.x, the exec function cannot change this lookup and will never find the name you defined unless you add an argument with the scope you want the result to be evaluated.

exec(some_code, globals())

For that to work, you need to add global my_var inside the function to make sure the lookup will work.

Keep in mind these things will be inserted in the global namespace of your module...

BTW, why do you need exec or eval? Why can't you add real functions to your algorithm list?


As side note, I can see you don't change the algorithm var on your function, but if you do it'll introduce some undesired side effects because the default value you created is mutable and will be used on all function calls.

For safety, change it to None and create a new list if needed.

OTHER TIPS

This doesn't attempt to answer the question, but rather expands the test-cases so the behavior can be seen better and preserved for reference. The results come from Python 3.2.2 on Windows. See JBernardo's answer for a "why" this behavior occurs.

From global scope ("toy example"):

>>> foo = "global-foo"
>>> exec('foo = "global-bar"')
>>> foo
'global-bar'

In a function (full context):

>>> def setIt():
        foo = "local-foo"
        exec('foo = "local-bar"')
        return foo

foo = "global-foo"
>>> setIt()
'local-foo'
>>> foo
'global-foo'

With specifying globals() (does not work for locals() at all):

>>> def setIt():
        foo = "local-foo"
        exec('foo = "local-bar"', globals())
        return foo

>>> foo = "global-foo"
>>> setIt()
'local-foo'
>>> foo
'local-bar'

Happy coding.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top