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.

Revisions

  1. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion python-closures.py
    Original file line number Diff line number Diff line change
    @@ -24,7 +24,7 @@
    # Python creates a *local variable*, but not modifies
    # the closured one. To avoid it, use `nonlocal` directive,
    # specifying explicitly that the variable is free (closured).
    # (Thanks to @joseanpg)
    # (Thanks to @joseanpg). See also http://www.python.org/dev/peps/pep-3104/
    #
    #
    # by Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com>
  2. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 96 additions and 58 deletions.
    154 changes: 96 additions & 58 deletions python-closures.py
    Original file line number Diff line number Diff line change
    @@ -19,13 +19,13 @@
    # refer global variables (which are also free
    # in this case for the function) via "globals()"
    #
    # 5. 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. However, in case of
    # objects only *reference* is copied, so we may modify
    # the content (keys/properties) of a particular object.
    # 5. By default, if there is an *assignment* to the
    # identifier with the same name as a closured one,
    # Python creates a *local variable*, but not modifies
    # the closured one. To avoid it, use `nonlocal` directive,
    # specifying explicitly that the variable is free (closured).
    # (Thanks to @joseanpg)
    #
    #
    # by Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com>
    #
    @@ -106,7 +106,7 @@ def baz(z):

    # ------ 2. Function without free variables does not closure --------

    # Let's show that if a function isn't use free variables
    # Let's show that if a function doesn't use free variables
    # it doesn't save lexical environment vars
    def f1(x):
    def f2():
    @@ -160,67 +160,105 @@ def global_fn():
    global_fn() # OK, 100
    print(global_fn.__closure__) # None

    # ------ 5. 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, y):
    # first child
    def child1(dx):
    x = dx # ! create just *local* "x", but not modify closured!
    print("Locals:", locals()) # {'x': 20, 'dx': 20}
    # ------ 5. By default assignment creates a local var. -----------------
    # ------ User `nonlocal` to capture the same name closured variable. ---

    # Since assignment to an undeclared identifier in Python creates
    # a new variable, it's hard to distinguish assignment to a closured
    # free variable from the creating of the new local variable. By default
    # Python strategy is to *create a new local variable*. To specify, that
    # we want to update exactly the closure variable, we should use
    # special `nonlocal` directive. However, if a closured variable is of
    # an object type (e.g. dict), it's content may be edited without
    # specifying `nonlocal` directive.

    # "x" is a simple number,
    # "y" is a dictionary
    def create(x, y):

    # this simplified example uses
    # "getX" / "setX"; only for educational purpose
    # in real code you rather would use real
    # properties (getters/setters)

    # this function uses
    # its own local "x" but not
    # closured from the "create" function
    def setX(newX):
    x = newX # ! create just *local* "x", but not modify closured!
    # and we cannot change it via e.g.
    # child1.__closure__[0] = dx, since tuples are *immutable*

    # OTOH, we can change the *content* if
    # closured binding's value is object;
    # "y" is an dictionary; add/set a new key "foo" to it
    y["foo"] = 100

    # second child
    def child2(dx):
    print("Current x: ", x)
    print("New x: ", dx)
    print("Current y:", y)
    print("Locals:", locals()) #

    # return dict
    # and this one already sets
    # the closured one; it may then
    # be read by the "getX" function
    def setReallyX(newX):
    # specify explicitly that "x"
    # is a free (or non-local) variable
    nonlocal x
    # and modify it
    x = newX

    # as mentioned, if we deal with
    # non-primitive type, we may mutate
    # contents of an object without `nonlocal`
    # since objects are passed by-reference (by-sharing)
    # and we modify not the "y" itself (i.e. not *rebound* it),
    # but its content (i.e. *mutate* it)
    def modifyYContent(foo):
    # add/set a new key "foo" to "y"
    y["foo"] = foo

    # getter of the "x"
    def getX():
    return x

    # getter of the "y"
    def getY():
    return y

    # return our messaging
    # dispatch table
    return {
    "child1": child1,
    "child2": child2
    "setReallyX": setReallyX,
    "setX": setX,
    "modifyYContent": modifyYContent,
    "getX": getX,
    "getY": getY
    }

    # 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*!
    # it captures only "y":
    # (<cell at 0x01428AB0: dict object at 0x0142D0C0>,) "y": {}
    # create our object
    instance = create(10, {})

    print(children["child1"].__closure__) # None!
    # "setX" does *not* closure "x" since uses *assignment*!
    # it doesn't closuse "y" too, since doesn't use it:
    print(instance["setX"].__closure__) # None

    # do *not* modfiy closured "x" but
    # do *not* modify closured "x" but
    # just create a local one
    # Locals: {'y': {}, 'x': 20, 'dx': 20}
    children["child1"](20)
    instance["setX"](100)

    # meanwhile, "child1" captrues both: "x" and "y":
    # test with a getter
    print(instance["getX"]()) # still 10

    # test with a "setReallyX", it closures only "x"
    # (
    # <cell at 0x01448AD0: int object at 0x1E1FEDF8>, "x": 10
    # <cell at 0x01448AB0: dict object at 0x0144D4B0> "y": {"foo": 100}, modified by "child1"
    # )
    print(children["child2"].__closure__)
    print(instance["setReallyX"].__closure__)
    instance["setReallyX"](100)

    # test again with a getter
    print(instance["getX"]()) # OK, now 100

    # "modifyYContent" captrues only "y":
    # (
    # <cell at 0x01448AB0: dict object at 0x0144D4B0> "y": {}
    # )
    print(instance["modifyYContent"].__closure__)

    # we may modify content of the
    # closured variable "y"
    instance["modifyYContent"](30)

    # Current x: 20
    # Current y: {'foo': 100} (changed by "child1" closure)
    # New x: 30
    # Locals: {'y': {'foo': 100}, 'x': 10, 'dx': 30}
    children["child2"](30)
    print(instance["getY"]()) # {"foo": 30}
  3. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 25 additions and 6 deletions.
    31 changes: 25 additions & 6 deletions python-closures.py
    Original file line number Diff line number Diff line change
    @@ -23,7 +23,9 @@
    # 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.
    # copies all closured variables. However, in case of
    # objects only *reference* is copied, so we may modify
    # the content (keys/properties) of a particular object.
    #
    # by Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com>
    #
    @@ -164,28 +166,34 @@ def global_fn():
    # are *immutable*! I.e. we can't modify closured data
    # but just create a new local variable

    def parent(x):
    def parent(x, y):
    # 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*

    # OTOH, we can change the *content* if
    # closured binding's value is object;
    # "y" is an dictionary; add/set a new key "foo" to it
    y["foo"] = 100

    # second child
    def child2(dx):
    print("Current x: ", x)
    print("New x: ", dx)
    print("Current y:", y)
    print("Locals:", locals()) #
    #x = dx

    # return dict
    return {
    "child1": child1,
    "child2": child2
    }

    # create children
    children = parent(10)
    children = parent(10, {})

    # {
    # 'child1': <function child1 at 0x013E9A50>,
    @@ -194,14 +202,25 @@ def child2(dx):
    print(children)

    # child1 does *not* closure "x" since uses *assignment*!
    # it captures only "y":
    # (<cell at 0x01428AB0: dict object at 0x0142D0C0>,) "y": {}

    print(children["child1"].__closure__) # None!

    # do *not* modfiy closured "x" but
    # just create a local one
    # Locals: {'x': 20, 'dx': 20}
    # Locals: {'y': {}, 'x': 20, 'dx': 20}
    children["child1"](20)

    # meanwhile, "child1" captrues both: "x" and "y":
    # (
    # <cell at 0x01448AD0: int object at 0x1E1FEDF8>, "x": 10
    # <cell at 0x01448AB0: dict object at 0x0144D4B0> "y": {"foo": 100}, modified by "child1"
    # )
    print(children["child2"].__closure__)

    # Current x: 20
    # Current y: {'foo': 100} (changed by "child1" closure)
    # New x: 30
    # Locals:
    # Locals: {'y': {'foo': 100}, 'x': 10, 'dx': 30}
    children["child2"](30)
  4. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 11 additions and 4 deletions.
    15 changes: 11 additions & 4 deletions python-closures.py
    Original file line number Diff line number Diff line change
    @@ -14,7 +14,12 @@
    # which uses free variables -- *all* previous
    # levels save the lexical environment
    #
    # 4. Closure in Python (in *contrast* with JavaScript!)
    # 4. Property __closure__ of *global* functions
    # is None, since a global function may
    # refer global variables (which are also free
    # in this case for the function) via "globals()"
    #
    # 5. 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
    @@ -52,6 +57,8 @@ def baz(z):
    # saves the content there (if not use free variables).

    # Lexical environment (closure) cells of "foo":
    # ("foo" doesn't use free variables, and moreover,
    # it's a global function, so its __closure__ is None)
    print(foo.__closure__) # None

    # "bar" is returned
    @@ -110,7 +117,7 @@ def f2():
    # its __closure__ is empty
    print(f2.__closure__) # None

    # ------ Closured is formed is there's most inner function -------
    # ------ 3. A closure is formed if there's most inner function -------

    # However, if we have another inner level,
    # then both functions save __closure__, even
    @@ -138,7 +145,7 @@ def f3(): # but "f3" does
    print(f3.__closure__[0].cell_contents) # "x": 200
    print(f3()) # 200

    # ------ 3. Global functions do not closure -------------------------
    # ------ 4. Global functions do not closure -------------------------

    # Global functions also do not save __closure__
    # i.e. its value always None, since may
    @@ -151,7 +158,7 @@ def global_fn():
    global_fn() # OK, 100
    print(global_fn.__closure__) # None

    # ------ 4. Does't use assignmnet; closures in Python are immutable --
    # ------ 5. 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
  5. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 4 additions and 2 deletions.
    6 changes: 4 additions & 2 deletions python-closures.py
    Original file line number Diff line number Diff line change
    @@ -31,12 +31,14 @@ def bar(y):
    q = 10
    # inner function "baz"
    def baz(z):
    print(locals()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
    print(vars()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
    print("Locals: ", locals())
    print("Vars: ", vars())
    return x + y + q + z
    return baz
    return bar

    # Locals: {'y': 20, 'x': 10, 'z': 30, 'q': 10}
    # Vars: {'y': 20, 'x': 10, 'z': 30, 'q': 10}
    print(foo(10)(20)(30)) # 70

    # Explanation:
  6. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion python-closures.py
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # "Undestanding Python's closures".
    # "Understanding Python's closures".
    #
    # Tested in Python 3.1.2
    #
  7. DmitrySoshnikov revised this gist Nov 15, 2010. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion python-closures.py
    Original file line number Diff line number Diff line change
    @@ -26,9 +26,10 @@

    # Define a function
    def foo(x):
    """docstring for foo"""
    # inner function "bar"
    def bar(y):
    q = 10
    # inner function "baz"
    def baz(z):
    print(locals()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
    print(vars()) # {'y': 20, 'x': 10, 'z': 30, 'q': 10}
  8. DmitrySoshnikov created this gist Nov 15, 2010.
    197 changes: 197 additions & 0 deletions python-closures.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,197 @@
    # "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)