2

I would like to dynamically construct the enclosing environment's namespace of a closure in such a way that the code inside the closure can access this dynamic content. Here is the simplest toy example that illustrated my issue:

def f():
    exec("Y=7",locals())    
    def closure():
        v=eval("Y*2")
        return v  
    return closure

When I use this code, here's what happens:

In [21]: Q = f()

In [22]: Q
Out[22]: <function __main__.closure>

In [23]: Q()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-23-51e72ed661f9> in <module>()
----> 1 Q()

<ipython-input-20-a47050c4450a> in closure()
      3
      4     def closure():
----> 5         return eval("Y*2")
      6
      7     return closure

<string> in <module>()

NameError: name 'Y' is not defined

So, in this case, the use of "exec" to add the bound variable "Y" to the closure's environment did not work. However, I cannot use a bare exec either, because of the nested function.

I considered using globals() instead of locals():

def f():
    exec("Y=7",globals())

    def closure():
        return eval("Y*2")

    return closure

## -- End pasted text --

In [27]: Q=f()

In [28]: Q
Out[28]: <function __main__.closure>

In [29]: Q()
Out[29]: 14

So, that solves my immediate problem, but what if I make the "exec" block have a mutable value? For example, if I change f() assign different values to Y, we have a new problem:

def f(z):
    exec("Y="+str(z),globals())

    def closure():
        return eval("Y*2")

    return closure

## -- End pasted text --

In [33]: Q=f(2)

In [34]: Q
Out[34]: <function __main__.closure>

In [35]: Q()
Out[35]: 4

In [36]: G=f(4)

In [37]: G()
Out[37]: 8

In [38]: Q()
Out[38]: 8 #doh!

So, what I need is a way to have the bound variable "Y" be located in the namespace of the closure/function environment, not in the "local()" (whatever that is) or "global()", due to above issue.

It seems that "exec" is not able to execute in this particular context. Would I need to exec the closure itself? This seemed to work.

def f(z):
    exec("Y="+str(z),locals())

    exec("def closure():\n return Y*2",locals())

    return eval("closure")

## -- End pasted text --

In [50]: G=f(3)

In [51]: G()
Out[51]: 6

In [52]: Q=f(5)

In [53]: Q()
Out[53]: 10

In [54]: G()
Out[54]: 6

In [55]: Q()
Out[55]: 10 #:-)

Now, what if I have a more complicated object for Y, like a function?

def f(z,args):
  exec("Y= lambda " + args[0] + ","+args[1]+":"+args[0]+"*2+"+args[1]+"*3",locals())

  exec("def closure(x,y):\n return z*Y(x,y)",locals())

  return eval("closure")

## -- End pasted text --

In [64]: Q=f(2,['a','b'])

In [65]: Q(2,2)
Out[65]: 20

In [68]: G=f(3,['a','b'])

In [69]: G(2,-1)
Out[69]: 3

In [70]: Q(2,2)
Out[70]: 20

This seems to work as well.

However, I was wondering if there were a more elegant way to dynamically construct a closure's environment.

I am using IPython with 2.7.11

Python 2.7.11 |Anaconda 2.4.0 (64-bit)| (default, Jan 19 2016, 12:08:31) [MSC v.1500 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.

IPython 4.0.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: exec("x=2")

In [2]: x
Out[2]: 2

In [3]:
  • 1
    Why do you want to do that? Why not just create Y as a dictionary, populate it dynamically, and then have the nested function access its keys? – BrenBarn Mar 10 '16 at 20:44
  • This shouldn't have a Python 2.7 tag. You're using `exec()` as a function. In Python 2.x it's a statement. I must surmise you are actually using Python 3.x. – kindall Mar 10 '16 at 20:48
  • @BrenBarn that would work if I had some basic structure on what the functions could be. However, I am parsing a yaml file that can contain arbitrary functions, arguments, and parameters. I need to parse the yaml and build up a function that is composed of sub-functions. Eg: f(x,y)=g(h(y),x) –  Mar 10 '16 at 20:50
  • @kindall no, this is python 2.7.11 –  Mar 10 '16 at 20:51
  • @BrenBarn I have a function composed of other functions, all of which are specified in a yaml file that I cannot assume to know the contents of (except for the fields and their content type). So, I need to first get the sub-functions into the closure's environment then use the closure to execute the top level function. This way, I am relying on the bindings across the closure to ensure that I have executed a valid function. –  Mar 10 '16 at 20:57
  • @BrenBarn is it always possible to use a dictionary like that, even when you don't know how to combine the key:value pares until runtime? –  Mar 10 '16 at 20:58
  • Huh. Didn't know about the function-like call syntax using a tuple. TIL. Carry on. – kindall Mar 10 '16 at 20:59
  • @kindall it is backward-compatibility syntax. [More eval/exec/compie secrets](http://stackoverflow.com/questions/2220699/whats-the-difference-between-eval-exec-and-compile-in-python). – Antti Haapala -- Слава Україні Mar 10 '16 at 21:05

1 Answers1

1

You cannot do closures this way in Python 2 or 3; no matter how much you'd try to exec, eval and such, the fact that Python compiler does not see a variable assignment in any of the surrounding function scopes of closure means that closure will be looking up Y in global scope instead.

You can verify this using dis.dis, the byte code would use LOAD_GLOBALs to access all of your "closure" variables.

You need to exec the whole code for def f(z):... in one go.


The latter 2 examples seem to work, but this one:

def f(z):
    exec("Y="+str(z),locals())
    exec("def closure():\n return Y*2",locals())
    return eval("closure")

would just use the locals() as globals to exec - that is again no proper closure. Instead of closures, you're compiling new function instances that are bound to their own global environment.

The same code could be written in a more fool-proof manner as

def f(z):
    globs = {'Y': z}
    exec('def closure():\n return Y*2', globs)
    return globs['closure']
  • How come my latter two examples seemed to work? Seemed like I can just exec the inner function –  Mar 10 '16 at 20:52
  • Also, I'm using python 2.7.11 –  Mar 10 '16 at 20:53
  • Thanks for clarifying...so when you say their "own" global environment, you mean that "closure" (not really a closure) has its own environment that is separate from the module's, correct? What I am trying to do is avoid the possibility of namespace clashes. –  Mar 10 '16 at 21:00
  • Yes, this would do it. I'd use this only if I knew there were only a handful. – Antti Haapala -- Слава Україні Mar 10 '16 at 21:04
  • Yep, I'm implementing this using "f(z)" as a function factory basically, which spits out these dynamically constructed functions in their own namespaces (even if they each have the same variable names in the configuration yaml file). I really like the dictionary approach...not familiar with that way of doing it. Thanks again. Ill accept! –  Mar 10 '16 at 21:06
  • module globals are just dictionaries/mappings in Python. – Antti Haapala -- Слава Україні Mar 10 '16 at 21:17