4

Let's say I want to implement an identity decorator with exec (ie, it shouldn't do absolutely nothing to a function without parameters). When I try to define a closure using that decorator, the scope survives the end of the f_factory function and changes what comes next.

I want to understand why does the last print return "1b" and not "1".

def exec_identity(f):

    gl = globals()
    gl.update({'f':f})

    exec "def idfun(): return f()" in gl, locals()
    return idfun


class CallableClass(object):
    def __init__(self, s):
        self.s = s

    def make_callable(self):
        def f_factory(s):
            def f():
                print s

            return exec_identity(f)
            #return f
        return f_factory(self.s)




c1 = CallableClass("1")
f1 = c1.make_callable()
f1()
c1.s = "1b"
f1()
f1b = c1.make_callable()
f1b()
f1()

"""
Result:

1
1
1b
1b
"""

I know that if I can leave the exec statement like this for it to work as expected:

exec "def idfun(): return f()" in {'f':f}, locals()
Zah
  • 6,394
  • 1
  • 22
  • 34
  • Please check this: http://stackoverflow.com/questions/392349/modify-bound-variables-of-a-closure-in-python – PasteBT Nov 22 '13 at 23:43

1 Answers1

3

It has to do with the fact that

gl = globals()
gl.update({'f':f})

in all cases works on the same object.

So the global f() is exchanged with a new one, and is called. The old one, along with its closure, gets lost.

gl = dict(globals())
gl.update({'f':f})

prevents that by copying the globals() dict.

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • @Zah Updated answer as well. – glglgl Nov 22 '13 at 23:37
  • 1
    According to the docs: "globals(): Return a dictionary representing the current global symbol table." But apparently that doesn't mean it is a read only copy, but it also modifies the actual global symbols... – Zah Nov 22 '13 at 23:58
  • 1
    @Zah Right. The doc is ambiguous in this concern. "representing" might mean that it is already a copy, or taht it really returns the dicht the global symol table actually uses (the latter seems to be the case). As a normal `dict`, it cannot be read-only; so obviously you indeed modify the real globals. – glglgl Nov 23 '13 at 08:40