Last active
August 29, 2015 14:04
-
-
Save sprin/dac580277cebd56be1f2 to your computer and use it in GitHub Desktop.
Python "closures"
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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