Skip to content

Instantly share code, notes, and snippets.

@sprin
Last active August 29, 2015 14:04
Show Gist options
  • Save sprin/dac580277cebd56be1f2 to your computer and use it in GitHub Desktop.
Save sprin/dac580277cebd56be1f2 to your computer and use it in GitHub Desktop.
Python "closures"
"""
A little demo of some of the quirks of Python "closures" with inner functions.
Function closures allow a function to access nonlocal variables in the
enclosing scope, even when invoked outside it's immediate lexical scope.
In languages centered around mutable data types, you would expect to be
able to mutate nonlocals in a closure.
MDN has an excellent rundown of closures, with examples in Javascript.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures
Do not think of inner functions as just creating a new inner variable
namespace inside a function! Inner functions are not a good tool to
manage cluttered function namespaces. Just write another non-nested function
when you want a new namespace.
Python 3 has the `nonlocal` keyword that resolves this. Good for you if you
don't ever need to maintain a Python 2 program, or a library compatible
with Python 2 and 3!
Note that there are some workarounds for the limitations of inner functions,
but they all have various tradeoffs:
http://stackoverflow.com/a/18503395
If you need functions with access to mutable data in the enclosing scope, use
classes instead!
"""
def outer_zero():
foo = 'bar'
print 'outer_zero initializes foo: foo = {0}'.format(foo)
def inner_zero():
print 'inner_zero sees: foo = {0}'.format(foo)
print 'Sweet! With inner functions, we have closures, right?'
print "Let's take a closer look"
inner_zero()
def outer_one():
def inner_one():
foo = 'bar'
print 'inner_one initializes foo: foo = {0}'.format(foo)
inner_one()
def inner_two():
try:
print 'inner_two sees: foo = {0}'.format(foo)
except:
print "inner_two could not access foo!"
inner_two()
try:
print 'outer_one sees: foo = {0}'.format(foo)
except:
print "outer_one could not access foo!"
print 'So far so good, variables are private to inner functions'
def outer_two():
foo = 'bar'
print 'outer_two initializes foo: foo = {0}'.format(foo)
def inner_three():
try:
foo = 'qux'
print 'inner_three mutated foo... or did it? foo = {0}'.format(foo)
except Exception:
print 'inner_three could not mutate foo!'
# non_local foo
# Using `non_local` ought to fix this issue... in Python 3!
# In Python 2, this will raise a syntax error because the nonlocal
# keyword does not exist in Python 2.
inner_three()
def inner_four():
print 'inner_four sees: foo = {0}'.format(foo)
inner_four()
print 'outer_one sees: foo = {0}'.format(foo)
print "Whoa hold on. We can't mutate vars inside an inner function!"
print "That's... not what you would expect from a closure!"
def make_adder(x):
"""
Classic adder closure with no mutation of vars in outer scope.
"""
def inner_adder(y):
return x + y
return inner_adder
def make_counter():
"""
Classic counter closure with mutation of vars in outer scope.
"""
counter = 0
def inner_counter():
try:
counter = counter + 1
except:
print 'inner_counter could not increment counter!'
return inner_counter
if __name__ == '__main__':
print (
"Let's see what does and doesn't work with Python inner functions as "
"closures."
)
print ''
outer_zero()
print ''
outer_one()
print ''
outer_two()
print ''
print "Let's look at some examples that actually do stuff"
print ''
print 'an adder using a closure'
add_five = make_adder(5)
print 'add_five(1) = {0}'.format(add_five(1))
print 'Our adder works because no mutation of vars in the outer scope.'
print ''
print 'a counter using a closure'
my_counter = make_counter()
print 'my_counter() = {0}'.format(my_counter())
print (
'Our counter does not work because we cannot mutate vars in the '
'outer scope'
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment