Read/Write Python Closures
To expand on Ignacio's answer:
def counter():
count = 0
def c():
nonlocal count
count += 1
return count
return c
x = counter()
print([x(),x(),x()])
gives [1,2,3] in Python 3; invocations of counter()
give independent counters. Other solutions - especially using itertools
/yield
are more idiomatic.
Closures in Python - an example
Here are 3 options:
use a list for your counter:
def make_progress_report(n):
i = [0]
def progress_report():
i[0] = i[0] + 1
if i[0] % n == 0:
print i[0]
return progress_reportuse itertools.count to track your counter:
from itertools import count
def make_progress_report(n):
i = count(1)
def progress_report():
cur = i.next()
if cur % n == 0:
print cur
return progress_reportUse nonlocal for your counter (Python 3+ only!):
def make_progress_report(n):
i = 0
def progress_report():
nonlocal i
i = i + 1
if i % n == 0:
print i
return progress_report
Can you explain closures (as they relate to Python)?
Closure on closures
Objects are data with methods
attached, closures are functions with
data attached.
def make_counter():
i = 0
def counter(): # counter() is a closure
nonlocal i
i += 1
return i
return counter
c1 = make_counter()
c2 = make_counter()
print (c1(), c1(), c2(), c2())
# -> 1 2 1 2
Closure in python?
Python 2.x has a syntax limitation that doesn't allow to capture a variable in read/write.
The reason is that if a variable is assigned in a function there are only two possibilities:
- the variable is a global and has been declared so with
global x
- the variable is a local of the function
more specifically it's ruled out that the variable is a local of an enclosing function scope
This has been superseded in Python 3.x with the addition of nonlocal
declaration. Your code would work as expected in Python 3 by changing it to
def make_adder_and_setter(x):
def setter(n):
nonlocal x
x = n
return (lambda y: x + y, setter)
The python 2.x runtime is able to handle read-write closed over variable at a bytecode level, however the limitation is in the syntax that the compiler accepts.
You can see a lisp compiler that generates python bytecode directly that creates an adder closure with read-write captured state at the end of this video. The compiler can generate bytecode for Python 2.x, Python 3.x or PyPy.
If you need closed-over mutable state in Python 2.x a trick is to use a list:
def make_adder_and_setter(x):
x = [x]
def setter(n):
x[0] = n
return (lambda y: x[0] + y, setter)
CLOSURE : retrieve a variable given with the outer function
__closure__
is an (read-only) attribute of a user-defined function object:
it is tuple (or None) of cell
s that contain bindings for the function’s free variables.
A cell
object has the (read & write) attribute cell_contents
. This can be used to get/set the value of the cell.
Note cell
types can be accessed through types
module
code
objects are an internal type used by the interpreter and accessible by the user.
co_freevars
is a tuple containing the names of free variable
Here an example on how to get the value of the function's parameter and its identifier:
# html_tag: from the question
print_h1 = html_tag('h1')
# identifier of the parameter - with code obj
print(print_h1.__code__.co_freevars[0])
#tag
# value of the function's parameter - read
print(print_h1.__closure__[0].cell_contents)
#h1
# value of the function's parameter - write
print_h1.__closure__[0].cell_contents = 'h19'
print_h1('a')
#<h19>a<h19>
Why can't Python increment variable in closure?
You can't re-bind closure variables in Python 2. In Python 3, which you appear to be using due to your print()
, you can declare them nonlocal
:
def foo():
counter = 1
def bar():
nonlocal counter
counter += 1
print("bar", counter)
return bar
bar = foo()
bar()
Otherwise, the assignment within bar()
makes the variable local, and since you haven't assigned a value to the variable in the local scope, trying to access it is an error.
In Python 2, my favorite workaround looks like this:
def foo():
class nonlocal:
counter = 1
def bar():
nonlocal.counter += 1
print("bar", nonlocal.counter)
return bar
bar = foo()
bar()
This works because mutating a mutable object doesn't require changing what the variable name points to. In this case, nonlocal
is the closure variable and it is never reassigned; only its contents are changed. Other workarounds use lists or dictionaries.
Or you could use a class for the whole thing, as @naomik suggests in a comment. Define __call__()
to make the instance callable.
class Foo(object):
def __init__(self, counter=1):
self.counter = counter
def __call__(self):
self.counter += 1
print("bar", self.counter)
bar = Foo()
bar()
How to maintain state in Python without classes?
You can also accomplish this with default arguments:
def try_match(m, re_match=re.compile(r'sldkjlsdjf').match):
return re_match(m)
since default arguments are only evaluated once, at module import time.
Or even simpler:
try_match = lambda m, re_match=re.compile(r'sldkjlsdjf').match: re_match(m)
Or simplest yet:
try_match = re.compile(r'sldkjlsdjf').match
This saves not only the re compile time (which is actually cached internally in the re module anyway), but also the lookup of the '.match' method. In a busy function or a tight loop, those '.' resolutions can add up.
Python - locals() and closure
locals() built-in function prints local symbol table which is bound to a code object, and filled up when interpreter receives a name in a source code.
Second example, when disassembled, will contain LOAD_GLOBAL foo bytecode instruction in b function code. This LOAD_GLOBAL instruction will move up the scope, find outer foo name and bind it to the code object by adding name offset into co_names attribute of the closure's (function b) code object.
locals() function prints local symbol table (as was said before, co_names attribute of function's code object).
Read more about code objects here.
confused by python3 closure
def gen(x):
def f():
nonlocal x # add this line
x += 1
return x
return f
print(gen(1)())
Why doesn't this closure modify the variable in the enclosing scope?
There are two "better" / more Pythonic ways to do this on Python 2.x than using a container just to get around the lack of a nonlocal keyword.
One you mentioned in a comment in your code -- bind to a local variable. There is another way to do that:
Using a default argument
def make_incrementer(start):
def closure(start = start):
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This has all the benefits of a local variable without an additional line of code. It also happens on the x = make_incrememter(100)
line rather than the iter = x()
line, which may or may not matter depending on the situation.
You can also use the "don't actually assign to the referenced variable" method, in a more elegant way than using a container:
Using a function attribute
def make_incrementer(start):
def closure():
# You can still do x = closure.start if you want to rebind to local scope
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
x = make_incrementer(100)
iter = x()
print iter.next()
This works in all recent versions of Python and utilizes the fact that in this situation, you already have an object you know the name of you can references attributes on -- there is no need to create a new container for just this purpose.
Related Topics
What Are Good Uses for Python3's "Function Annotations"
How to Turn Off Info Logging in Spark
How to Add a Custom Ca Root Certificate to the Ca Store Used by Pip in Windows
Does Python Have a Stack/Heap and How Is Memory Managed
How to Use Python to Execute a Curl Command
Best Way to Format Integer as String with Leading Zeros
How to Merge 200 CSV Files in Python
Pyspark: Split Multiple Array Columns into Rows
Mysql-Python Install Error: Cannot Open Include File 'Config-Win.H'
Using Moviepy, Scipy and Numpy in Amazon Lambda
How to Insert Pandas Dataframe via MySQLdb into Database
Python How to Pad Numpy Array with Zeros
Tkinter Vanishing Photoimage Issue
How to Use Python to Execute a Curl Command
How to Run Scrapy from Within a Python Script
How to Convert a Timezone Aware String to Datetime in Python Without Dateutil