#######################
#!/usr/bin/env python
def make_counter(start_num):
start = start_num
def counter():
start += 1
return counter
from_ten = make_counter(10)
from_three = make_counter(3)
print from_ten() # 10
print from_ten() # 11
print from_three() # 3
print from_ten() # 12
print from_three() # 4
####################
The error message is: "UnboundLocalError: local variable 'start'
referenced before assignment". The same thing happens if I omit start
and just use start_num directly.
How can I do it in Python?
In Python 3.0, you can do this:
def make_counter(start):
def counter():
nonlocal start
start += 1
return start # I assume you forgot this above
return counter
In Python 2.x, there is no nonlocal statement, so the best you can do
is improvise something like this:
def make_counter(start_num):
start = [start_num]
def counter():
start[0] += 1
return start[0]
return counter
You can access variables from an enclosing scope starting from Python
2.2 (from 2.1 with from __future__ import nested_scopes), but you
could not rebind those variables until Python 3.0 came out today.
Carl Banks
Depending on your version of Python, you need to do either (A) or (B).
(A) requires Python 3.0 IIRC.
> def make_counter(start_num):
> start = start_num
(B) replace prev line with: start = [start_num]
> def counter():
(A) add: nonlocal start
> start += 1
(B) replace prev line with: start[0] += 1
> return counter
>
> from_ten = make_counter(10)
> from_three = make_counter(3)
>
> print from_ten() # 10
> print from_ten() # 11
> print from_three() # 3
> print from_ten() # 12
> print from_three() # 4
> ####################
>
> The error message is: "UnboundLocalError: local variable 'start'
> referenced before assignment". The same thing happens if I omit start
> and just use start_num directly.
See http://www.python.org/dev/peps/pep-3104/ for more info.
Cheers,
Chris
--
Follow the path of the Iguana...
http://rebertia.com
>
> How can I do it in Python?
> I just came across http://www.perl.com/pub/a/2002/05/29/closure.html
> and wanted to try the "canonical example of closures" in Python. I
> came up with the following, but it fails:
>
> #######################
> #!/usr/bin/env python
>
> def make_counter(start_num):
> start = start_num
> def counter():
> start += 1
> return counter
The other answers you got will work, but here's an alternative to
consider: you can convert the function into a generator and then just
move the variable inside.
>>> def make_counter(start_num):
def counter():
start = start_num
while 1:
yield start
start += 1
return counter().next
>>> from_ten = make_counter(10)
>>> from_three = make_counter(3)
>>> print from_ten()
10
>>> print from_ten()
11
>>> print from_three()
3
>>> print from_ten()
12
>>> print from_three()
4
>>>
While I realise you are just trying a simple example, if you actually
wanted the code the truly Pythonic way would be to use the existing
libraries:
>>> import itertools
>>> def make_counter(start_num):
return itertools.count(start_num).next
>>> from_ten = make_counter(10)
>>> from_three = make_counter(3)
>>> print from_ten()
10
>>> print from_ten()
11
>>> print from_three()
3
>>> print from_ten()
12
>>> print from_three()
4
>>>
Never reinvent the wheel unless you really need a square one. :^)
--
Duncan Booth http://kupuguy.blogspot.com
Since no one has suggested it:
class make_counter(object):
def __init__(self, i):
self.i = i
def __call__(self):
i = self.i
self.i += 1
return i
James
--
James Stroud
UCLA-DOE Institute for Genomics and Proteomics
Box 951570
Los Angeles, CA 90095
With a class is the best way IMHO.
class make_counter(object):
def __init__(self, start_num):
self.x = start_num
def __call__(self):
x = self.x
self.x += 1
return x
--
Nick Craig-Wood <ni...@craig-wood.com> -- http://www.craig-wood.com/nick
Interesting...
You can also write it without nested functions / closures
def counter(x):
while 1:
yield x
x += 1
def make_counter(start_num):
return counter(start_num).next
I expect the machinery is similar between generators and closures, but
with generators it is a lot more obvious exactly which version of
which variable you are using!
In fact you can always do what you can do with a closure with the
above technique I think...
def make_closure(*args, **kwargs):
# initialisation to local vars
def closure():
# stuff, being careful with nonlocal args & kwargs
return result
vs
def closure(*args, **kwargs):
# initialisation to local vars
while 1:
# normal stuff using args and kwargs
yield result
def make_closure(*args, **kwargs):
return closure(*args, **kwargs).next
I still prefer doing it explicitly with a class though ;-)