The following (absurd but illustrative) code works as expected by mutating the list defined in the enclosing function:
def outside1():
l = list('abcd')
def inside(a):
print "Before - %i: %r" % (id(l), l)
l.append(a)
print "After - %i: %r\n" % (id(l), l)
return inside
f = outside1()
[f(c) for c in 'efgh']
This code also works showing that an immutable defined in an enclosing scope is accessible within the enclosed scope:
def outside2():
t = tuple('abcd')
def inside():
print "%i: %r" % (id(t), t)
return inside
outside2()()
However this fails with local variable 't' referenced before assignment:
def outside3():
t = tuple('abcd')
def inside(a):
print "Before - %i: %r" % (id(t), t)
t = t + (a,)
print "After - %i: %r\n" % (id(t), t)
return inside
f = outside3()
[f(c) for c in 'efgh']
Can someone explain what is going on here? My first guess was that I can mutate but not assign to enclosing scope, but I would have at least expected the before print statement to work given that the outside2 works.
Python detects the scope of names statically while compiling: A name that is assigned to inside a function becomes local to that function. So the line
renders
tlocal toinside(), and any look-up oftinsideinside()will try to look up a local variable ofinside(). When the above line is exceuted,tdoes not exist yet, hence the error.In Python 3.x, you can resolve that problem by explicitly declaring
tasnonlocal:All this is completely unrelated to mutability. Your example using lists doesn’t reassign the name
l, while the example using a tuple does reassignt; and this is the important difference, not mutability.