Friday, October 16, 2009

Python closures explanation

While trying to figure out what exactly people meant when they said that python didn't have true closures, I found this excellent response that describes the issue perfectly.




Joel VanderWerf wrote:
> ...
>
> But in Python, the inner function only has access to the _object_, not
> to the original variable which refers to the object, and so you have to
> "box" the value inside an object if you want to have shared references.

No, you still don't undestand the issue properly. You're getting closer,
but not quite there yet.

y = 0

def foo():
global y
x = 0
def bar():
print x, y
bar()
x = y = 1
bar()
x = y = 2
bar()
x = y = 3
bar()
x = y = 4
bar()
x = y = 5

foo()

0 0
1 1
2 2
3 3
4 4

Now just as in Ruby, 0, 1, 2, 3, 4 are separate objects. It is literally
impossible to change their values. All you can do is change bindings TO
the variables. So obviously "bar" is really inheriting the _bindings_
for x and y because it is the _bindings_ that are changing.

So what's the problem? Consider this program:

y = 0

def foo():
x = 0
def bar():
print x, y
def change(z):
global y
x = y = z

change(1)
bar()
change(2)
bar()
change(3)
bar()
change(4)
bar()

foo()

The output is:

0 1
0 2
0 3
0 4

This is because Python has always adopted the rule that if you assign to
a local variable it springs into existence -- as a local. If you want to
overwrite a global variable, you have to specifically say: "please
overwrite this global." It prevents people from accidentally overwriting
globals with locals.

But there is no syntax for saying: "please overwrite this intermediate
closure variable." It would be as easy as adding a keyword
"intermediate" or "closure". It's purely a syntactic issue.

_But_ the addition would be useless in practice because this problem
almost never arises in real code. Its only purpose would be to answer
Lisp and Ruby advocates who want to say that Python doesn't really have
closures. I don't know whether Guido will ever add it for, er, closure,
but I do know that I have _never_ _once_ needed this keyword in hundreds
of thousands of lines of real Python code I have written. And I am thus
going to fight my temptation to go and bug Guido to do it so that we can
put an end to permathreads about Python's lack of closures. ;) If this
problem ever arose in my code (which it has not) then I could work
around it thus:

y = 0

def foo():
x = [0]
def bar():
print x[0], y
def change(z):
global y
x[0] = y = z

change(1)
bar()
change(2)
bar()
change(3)
bar()
change(4)
bar()

foo()

1 1
2 2
3 3
4 4

This should prove that the problem is really not deep in the Python
interpreter or anything like that. It would be a half day's work to
write the patch that added the "intermediate" keyword.

By the way, there was a time when Python really did lack closures but
there was a hacky workaround. This is where the idea that Python did not
have "real" closures became popular (because it was true). Then Python
added closures, but left out one minor feature and the minor feature
allowed the idea to persist even when it had ceased to be true.

--Paul Prescod

No comments:

Post a Comment