Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

How can I do this (from Perl) in Python? (closures)

1 view
Skip to first unread message

exco...@gmail.com

unread,
Dec 4, 2008, 12:18:53 AM12/4/08
to
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

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?

Carl Banks

unread,
Dec 4, 2008, 12:29:17 AM12/4/08
to
On Dec 3, 11:18 pm, excor...@gmail.com wrote:
> I just came acrosshttp://www.perl.com/pub/a/2002/05/29/closure.html

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

Chris Rebert

unread,
Dec 4, 2008, 12:31:44 AM12/4/08
to exco...@gmail.com, pytho...@python.org
On Wed, Dec 3, 2008 at 9:18 PM, <exco...@gmail.com> wrote:
> 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
>

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?

> --
> http://mail.python.org/mailman/listinfo/python-list
>

Duncan Booth

unread,
Dec 4, 2008, 3:50:21 AM12/4/08
to
exco...@gmail.com wrote:

> 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

James Stroud

unread,
Dec 4, 2008, 3:59:00 AM12/4/08
to
exco...@gmail.com wrote:
> 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?

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

http://www.jamesstroud.com

Nick Craig-Wood

unread,
Dec 4, 2008, 5:30:47 AM12/4/08
to
exco...@gmail.com <exco...@gmail.com> wrote:
> 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:
>
> 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?

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

Nick Craig-Wood

unread,
Dec 4, 2008, 7:30:47 AM12/4/08
to
Duncan Booth <duncan...@invalid.invalid> wrote:
> exco...@gmail.com wrote:
>
> > 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:
> >
> > 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

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 ;-)

0 new messages