When I run this code, I get this result:
15
15
I expect the output should be
15
17
but it is not. The question is: why?
def make_adder_and_setter(x):
def setter(n):
x = n
return (lambda y: x + y, setter)
myadder, mysetter = make_adder_and_setter(5)
print myadder(10)
mysetter(7)
print myadder(10)
You are setting a local variable
x
in thesetter()
function. Assignment to a name in a function marks it as a local, unless you specifically tell the Python compiler otherwise.In Python 3, you can explicitly mark
x
as non-local using thenonlocal
keyword:Now
x
is marked as a free variable and looked up in the surrounding scope instead when assigned to.In Python 2 you cannot mark a Python local as such. The only other option you have is marking
x
as aglobal
. You'll have to resort to tricks where you alter values contained by a mutable object that lives in the surrounding scope.An attribute on the
setter
function would work, for example;setter
is local to themake_adder_and_setter()
scope, attributes on that object would be visible to anything that has access tosetter
:Another trick is to use a mutable container, such as a list:
In both cases you are not assigning to a local name anymore; the first example uses attribute assignment on the
setter
object, the second alters thex
list, not assign tox
itself.Python 2.x has a syntax limitation that doesn't allow to capture a variable in read/write.
The reason is that if a variable is assigned in a function there are only two possibilities:
global x
more specifically it's ruled out that the variable is a local of an enclosing function scope
This has been superseded in Python 3.x with the addition of
nonlocal
declaration. Your code would work as expected in Python 3 by changing it toThe python 2.x runtime is able to handle read-write closed over variable at a bytecode level, however the limitation is in the syntax that the compiler accepts.
You can see a lisp compiler that generates python bytecode directly that creates an adder closure with read-write captured state at the end of this video. The compiler can generate bytecode for Python 2.x, Python 3.x or PyPy.
If you need closed-over mutable state in Python 2.x a trick is to use a list:
Your inner
def setter(n)
function defines its own local variablex
. That hides the otherx
variable that was a parameter ofmake_adder_and_setter
(makes a hole in the scope). So thesetter
function has no side effect. It just sets the value of an inner local variable and exits.Maybe it will be clear for you if you try the code below. It does exactly the same thing, just uses the name z instead of x.