3

Consider this (rather pointless) javascript code:

function make_closure() {
    var x = 123, y = 456;
    return function(varname) { return eval(varname) }
}

closure = make_closure()
closure("x") // 123
closure("y") // 456

The function returned from make_closure doesn't contain any references to scope variables, but still is able to return their values when called.

Is there a way to do the same in python?

def make_closure():
    x = 123
    return lambda varname: ...no "x" here...

closure = make_closure()
print closure("x") # 123

In other words, how to write a closure that would "know" about variables in defining scope without mentioning them explicitly?

georg
  • 211,518
  • 52
  • 313
  • 390

4 Answers4

5

This is probably the closest thing to an exact equivalent:

def make_closure():
  x = 123
  l = locals()
  return lambda(varname): eval(varname, None, l)

closure = make_closure()
print closure("x") # 123

Ultimately, your problem isn't that x wasn't captured by your local closure, but that your local scope wasn't passed into eval.

See http://docs.python.org/library/functions.html#eval for details on the eval function.

It probably goes without saying that this is not a very Pythonic thing to do. You almost never actually want to call eval in Python; if you think you do, step back and rethink your larger design. But when you do need to use it, you need to understand how it's different from Javascript eval. It's ultimately more powerful—it can be used to simulate dynamic scoping, or as a simplified version of exec, or to evaluate code in any arbitrarily-constructed environment—but that also means it's trickier.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • Yes, x gets captured by the closure, but as it seems Python provides no means for us to prove that (or the contrary). – georg Jun 20 '12 at 14:03
  • Python provides lots of ways to prove what's happening, even without using a debugger or examining the locals() argument. Amber's answer proves that the value of x is either copied or captured. If you change the value to something mutable, like [123] instead of 123, you can do closure()[0]=456, then call closure() again and get [456] instead of [123], which proves that it's actually captured. That still doesn't tell you whether the _name_ gets captured, because Python makes it difficult, and generally useless, to reference name bindings, but ultimately it doesn't matter, for the same reasons. – abarnert Jun 20 '12 at 16:55
2

People have already mentioned using locals(). Another, admittedly less flexible, approach is to use the new nonlocal keyword in Python 3:

>>> def make_closure():
...     x = 123
...     def return_var(varname):
...         nonlocal x
...         return eval(varname)
...     return return_var
... 
>>> closure = make_closure()
>>> closure("x")
123

This has the obvious downside of requiring you to explicitly label the variables from the outer scope that you wish to close over and reference.

Greg E.
  • 2,722
  • 1
  • 16
  • 22
  • Since the OP explicitly said "… without mentioning them explicitly", this doesn't really answer what he wants. Although, as with Amber's answer, you could argue that it answers what he _should_ want. – abarnert Jun 19 '12 at 22:41
  • @abarnert, that's really a moot point. There's no conceivable situation (unless my imagination is really failing me) in which a person writing this sort of function wouldn't have access to advance knowledge of the variables that would need to be closed over. Using `locals()` isn't fundamentally different, it's just maximally inclusive and less verbose. – Greg E. Jun 19 '12 at 22:47
  • @abarnert, FYI, I think your solution is the better (and obviously more concise) one, and I've upvoted it accordingly. That said, I still think the difference between using `locals()` and the more explicit `nonlocal` keyword approach is mostly academic. – Greg E. Jun 19 '12 at 22:55
  • I can easily conceive of such a situation: Imagine that your local environment has been modified by, e.g., doing lots of evals and execs. You have no way of knowing (statically) what variables have been defined. That's pretty much exactly what locals() is for. Sure, that's a very unpythonic situation, but no more so than the OP's question. Trying to let your caller access variables that you can't predict vs. trying to access variables that you can't predict you have… they're pretty much the same thing. – abarnert Jun 19 '12 at 22:55
  • @abarnert, that hypothetical situation has no bearing on this particular case, because the OP's question had him explicitly defining, within the immediately enclosing parent function, the variables that he intended to later reference. However, I certainly won't argue with you that there are other situations involving different kinds of code where using `locals()` would obviously be necessary. – Greg E. Jun 19 '12 at 22:59
  • True, but presumably he posted a simplest (and "rather pointless") working program to demonstrate the problem, not his actual use case, and his description of what he wants is accurate. Anyway, nonlocal is useful in all kinds of cases where in 2.x hackery involving locals is the only alternative, and it's the kind of thing the OP probably both is and should be interested in learning, so this is a useful answer no matter what. – abarnert Jun 19 '12 at 23:02
  • @abarnert, fair enough. My reading of his question was probably too limited and/or too literal. – Greg E. Jun 19 '12 at 23:04
1

You can close around variables in Python, approximately how you would in Javascript:

def foo():
  x = 123
  return lambda y: x+y

q = foo()

print q(5)

http://ideone.com/Q8iBg

However, there are some differences - for instance, you can't write to variables in the parent's scope (trying to write just creates a local-scoped variable instead). Also, Python's eval() doesn't include parent scopes.

You can bypass this for eval() by explicitly grabbing the locals from the parent scope and passing them into the child's eval() as its locals argument, but the real question here is why do you need to do this?

Amber
  • 507,862
  • 82
  • 626
  • 550
  • Even though this doesn't directly answer the question, the truth is that almost any program can (and should) be restructured to not need eval—in which case this answer tells you everything you need to know about closures. – abarnert Jun 19 '12 at 22:40
  • Agreed. Also I think your answer addresses this slightly better, so I've upvoted it. – Amber Jun 19 '12 at 22:44
0

In Python 2.x this requires some massive kludging. However, in Python 3.x the 'nonlocal' keyword was introduced to allow access to variables in the immediately enclosing scope.

It might be worth checking here for further information: nonlocal keyword in Python 2.x

Community
  • 1
  • 1
Jon Clements
  • 138,671
  • 33
  • 247
  • 280