Created
November 15, 2010 12:03
-
-
Save DmitrySoshnikov/700292 to your computer and use it in GitHub Desktop.
Revisions
-
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal 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). See also http://www.python.org/dev/peps/pep-3104/ # # # by Dmitry A. Soshnikov <dmitry.soshnikov@gmail.com> -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 96 additions and 58 deletions.There are no files selected for viewing
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 charactersOriginal 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. 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 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. 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* # 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 { "setReallyX": setReallyX, "setX": setX, "modifyYContent": modifyYContent, "getX": getX, "getY": getY } # create our object instance = create(10, {}) # "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* modify closured "x" but # just create a local one instance["setX"](100) # 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 # ) 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) print(instance["getY"]()) # {"foo": 30} -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 25 additions and 6 deletions.There are no files selected for viewing
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 charactersOriginal 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. 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, 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()) # # return dict return { "child1": child1, "child2": child2 } # create children 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: {'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: {'y': {'foo': 100}, 'x': 10, 'dx': 30} children["child2"](30) -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 11 additions and 4 deletions.There are no files selected for viewing
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 charactersOriginal 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. 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 # ------ 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 # ------ 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 # ------ 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 -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 4 additions and 2 deletions.There are no files selected for viewing
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 charactersOriginal 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: ", 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: -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ # "Understanding Python's closures". # # Tested in Python 3.1.2 # -
DmitrySoshnikov revised this gist
Nov 15, 2010 . 1 changed file with 2 additions and 1 deletion.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -26,9 +26,10 @@ # Define a function def foo(x): # 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} -
DmitrySoshnikov created this gist
Nov 15, 2010 .There are no files selected for viewing
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 charactersOriginal 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)