views:

1236

answers:

6

Where X is any programming language (C#, Javascript, Lisp, Perl, Ruby, Scheme, etc) which supports some flavour of closures.

Some limitations are mentioned in the Closures in Python (compared to Ruby's closures), but the article is old and many limitations do not exist in modern Python any more.

Seeing a code example for a concrete limitation would be great.

Related questions:

+3  A: 

The only difficulty I've seen people encounter with Python's in particular is when they try to mix non-functional features like variable reassignment with closures, and are surprised when this doesn't work:

def outer ():
    x = 1
    def inner ():
        print x
        x = 2
    return inner
outer () ()

Usually just pointing out that a function has its own local variables is enough to deter such silliness.

John Millikin
UnboundLocalError
J.F. Sebastian
you need a return inner
Moe
@Moe thanks ; @JF exactly. Closures work just like any other function, but for some reason people think they should perform magic when assigning variables.
John Millikin
My comment is too big. I've posted it as an answer.
J.F. Sebastian
+14  A: 

The most important limitation, currently, is that you cannot assign to an outer-scope variable. In other words, closures are read-only:

>>> def outer(x): 
...     def inner_reads():
...         # Will return outer's 'x'.
...         return x
...     def inner_writes(y):
...         # Will assign to a local 'x', not the outer 'x'
...         x = y
...     def inner_error(y):
...         # Will produce an error: 'x' is local because of the assignment,
...         # but we use it before it is assigned to.
...         tmp = x
...         x = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
5
>>> inner_error(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 11, in inner_error
UnboundLocalError: local variable 'x' referenced before assignment

A name that gets assigned to in a local scope (a function) is always local, unless declared otherwise. While there is the 'global' declaration to declare a variable global even when it is assigned to, there is no such declaration for enclosed variables -- yet. In Python 3.0, there is (will be) the 'nonlocal' declaration that does just that.

You can work around this limitation in the mean time by using a mutable container type:

>>> def outer(x):
...     x = [x]
...     def inner_reads():
...         # Will return outer's x's first (and only) element.
...         return x[0]
...     def inner_writes(y):
...         # Will look up outer's x, then mutate it.      
...         x[0] = y
...     def inner_error(y):
...         # Will now work, because 'x' is not assigned to, just referenced.
...         tmp = x[0]
...         x[0] = y
...         return tmp
...     return inner_reads, inner_writes, inner_error
... 
>>> inner_reads, inner_writes, inner_error = outer(5)
>>> inner_reads()
5
>>> inner_writes(10)
>>> inner_reads()
10
>>> inner_error(15)
10
>>> inner_reads()
15
Thomas Wouters
`nonlocal` as you mentined solves this problem. An inner class with `__call__` can solve it too (but version with a list is more succinct).
J.F. Sebastian
+1  A: 

@John Millikin

def outer():
    x = 1 # local to `outer()`

    def inner():
        x = 2     # local to `inner()`
        print(x)
        x = 3
        return x

    def inner2():
        nonlocal x
        print(x)  # local to `outer()`
        x = 4     # change `x`, it is not local to `inner2()`
        return x

    x = 5         # local to `outer()`
    return (inner, inner2)

for inner in outer():
    print(inner()) 

# -> 2 3 5 4
J.F. Sebastian
+2  A: 

Fixed in Python 3.0 via the nonlocal keyword. See here and search for "nonlocal".

For those of you who don't like using the web:

The nonlocal statement lets you assign to variables in outer (non-global) scopes.
-- some guy named "van Rossum" or something... ;)

Kevin Little
My comment is too big. I've posted it as an answer.
J.F. Sebastian
Hmmm ... presumably the nonlocal variable is shared between all instances of the closure? Making it rather like a static / class variable?
interstar
+1  A: 

@Kevin Little

nonlocal does not solves completely this problem:

x = 0 # global x
def outer():
    x = 1 # local to `outer`
    def inner():
        global x
        x = 2 # change global
        print(x) 
        x = 3 # change global
        return x
    def inner2():
##        nonlocal x # can't use `nonlocal` here
        print(x)     # prints global
##        x = 4      # can't change `x` here
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 3 3

On the other hand:

x = 0
def outer():
    x = 1 # local to `outer`
    def inner():
##        global x
        x = 2
        print(x) # local to `inner` 
        x = 3 
        return x
    def inner2():
        nonlocal x
        print(x)
        x = 4  # local to `outer`
        return x
    x = 5
    return (inner, inner2)

for inner in outer():
    print(inner())
# -> 2 3 5 4
J.F. Sebastian
A: 

The better workaround until 3.0 is to include the variable as a defaulted parameter in the enclosed function definition:

def f()
    x = 5
    def g(y, z, x=x):
        x = x + 1
The value of outer `x` will always be `5`.
J.F. Sebastian