Skip to content

Instantly share code, notes, and snippets.

@DmitrySoshnikov
Created November 15, 2010 12:03
Show Gist options
  • Select an option

  • Save DmitrySoshnikov/700292 to your computer and use it in GitHub Desktop.

Select an option

Save DmitrySoshnikov/700292 to your computer and use it in GitHub Desktop.
Understanding Python's closures
# "Undestanding Python's closures".
#
# Tested in Python 3.1.2
#
# General points:
#
# 1. Closured lexical environments are stored
# in the property __closure__ of a function
#
# 2. If a function does not use free variables
# it doesn't form a closure
#
# 3. However, if there is another inner level
# which uses free variables -- *all* previous
# levels save the lexical environment
#
# 4. Closure in Python (in *contrast* with JavaScript!)
# are *immutable*. I.e. we cannot change values
# of closured variables. I.e. Python does not
# use a scheme with *environment frames*, but just
# copies all closured variables.
#
# by Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com>
#
# (C) 2010 Mit Style License
# Define a function
def foo(x):
"""docstring for foo"""
def bar(y):
q = 10
def baz(z):
print(locals()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
print(vars()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
return x + y + q + z
return baz
return bar
print(foo(10)(20)(30)) # 70
# Explanation:
# ------ 1. Magic property "__closure__" ----------------------------
# All `free variables` (i.e. variables which are
# neither local vars, nor arguments) of "baz" funtion
# are saved in the internal "__closure__" property.
# Every function has this property, though, not every
# saves the content there (if not use free variables).
# Lexical environment (closure) cells of "foo":
print(foo.__closure__) # None
# "bar" is returned
bar = foo(10)
# Closure cells of "bar":
# (
# <cell at 0x014E7430: int object at 0x1E1FEDF8>, "x": 10
# )
print(bar.__closure__)
# "baz" is returned
baz = bar(20)
#
# Closure cells of "bar":
# (
# <cell at 0x013F7490: int object at 0x1E1FEE98>, "x": 10
# <cell at 0x013F7450: int object at 0x1E1FEDF8>, "y": 20
# <cell at 0x013F74B0: int object at 0x1E1FEDF8>, "q": 10
# )
#
print(baz.__closure__)
# So, we see that a "__closure__" property is a tuple
# (immutable list/array) of closure *cells*; we may refer them
# and their contents explicitly -- a cell has property "cell_contents"
print(baz.__closure__[0]) # <cell at 0x013F7490: int object at 0x1E1FEE98>
print(baz.__closure__[0].cell_contents) # 10 -- this is our closured "x"
# the same for "y" and "q"
print(baz.__closure__[1].cell_contents) # "y": 20
print(baz.__closure__[2].cell_contents) # "q": 10
# Then, when "baz" is activated it's own environment
# frame is created (which contains local variable "z")
# and merged with property __closure__. The result dictionary
# we may refer it via "locals()" or "vars()" funtions.
# Being able to refer all saved (closured) free variables,
# we get correct result -- 70:
baz(30) # 70
# ------ 2. Function without free variables does not closure --------
# Let's show that if a function isn't use free variables
# it doesn't save lexical environment vars
def f1(x):
def f2():
pass
return f2
# create "f2"
f2 = f1(10)
# its __closure__ is empty
print(f2.__closure__) # None
# ------ Closured is formed is there's most inner function -------
# However, if we have another inner level,
# then both functions save __closure__, even
# if some parent level doesn't use free vars
def f1(x):
def f2(): # doesn't use free vars
def f3(): # but "f3" does
return x
return f3
return f2
# create "f2"
f2 = f1(200)
# it has (and should have) __closure__
print(f2.__closure__) # (<cell at 0x014B7990: int object at 0x1E1FF9D8>,)
print(f2.__closure__[0].cell_contents) # "x": 200
# create "f3"
f3 = f2()
# it also has __closure__ (the same as "f2")
print(f3.__closure__) # (<cell at 0x014B7990: int object at 0x1E1FF9D8>,)
print(f3.__closure__[0].cell_contents) # "x": 200
print(f3()) # 200
# ------ 3. Global functions do not closure -------------------------
# Global functions also do not save __closure__
# i.e. its value always None, since may
# refer via globals()
global_var = 100
def global_fn():
print(globals()["global_var"]) # 100
print(global_var) # 100
global_fn() # OK, 100
print(global_fn.__closure__) # None
# ------ 4. Does't use assignmnet; closures in Python are immutable --
# Closures in Python (in *contrast* with JavaScript!)
# are *immutable*! I.e. we can't modify closured data
# but just create a new local variable
def parent(x):
# first child
def child1(dx):
x = dx # ! create just *local* "x", but not modify closured!
print("Locals:", locals()) # {'x': 20, 'dx': 20}
# and we cannot change it via e.g.
# child1.__closure__[0] = dx, since tuples are *immutable*
# second child
def child2(dx):
print("Current x: ", x)
print("New x: ", dx)
print("Locals:", locals()) #
#x = dx
# return dict
return {
"child1": child1,
"child2": child2
}
# create children
children = parent(10)
# {
# 'child1': <function child1 at 0x013E9A50>,
# 'child2': <function child2 at 0x013E9A98>
# }
print(children)
# child1 does *not* closure "x" since uses *assignment*!
print(children["child1"].__closure__) # None!
# do *not* modfiy closured "x" but
# just create a local one
# Locals: {'x': 20, 'dx': 20}
children["child1"](20)
# Current x: 20
# New x: 30
# Locals:
children["child2"](30)
@updogliu
Copy link

Is there a typo in line 79? Should it be 'baz'?

@tomaszputon
Copy link

correct, it should be 'baz'

@CMCDragonkai
Copy link

In python 3.6.8, running your code doesn't show the names of the closure variables, on the type and how many of them.

@nehavari
Copy link

nehavari commented Jan 2, 2020

thank you for a nice demo on closures. It is helpful. However, I observe one mismatch for baz index 0 is q(10), index 1 is x(10) and index 2 is y(20) so the order is q, x, y not x, y, q. It seems local variables get a place before the arguments in closure tuple.

@gufengxiaoyuehan
Copy link

@CMCDragonkai you can lookup their names in bar.func.co_cellvars . (works in python 3.8.1)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment