-1

I was learning about closures in Lua and came accross this code that works in Lua:

function newcounter()
    local i = 0
    function dummy()
        i = i + 1
        return i
    return dummy

x = newcounter()
print(x())
-- this outputs 1, i.e., dummy uses the i variable from its enclosing function newcounter

I thought Python also supports a similar closure. So I tried the following code in Python3:

def nc():
    i = 0
    def dummy():
        i = i + 1
        return i
    return dummy

x = nc()
print(x())

However, when I run the above Python code, I get the error that i is accessed before being assigned!

Traceback (most recent call last):
  File "/tmp/test.py", line 9, in <module>
     print(x())
  File "/tmp/test.py", line 4, in dummy
     i = i + 1
UnboundLocalError: local variable 'i' referenced before assignment

Does this mean Python does not support closures? Or am I misunderstanding something about closures?

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
Siap
  • 1
  • 1
  • I think you're misunderstanding what a closure is. How would you describe it in your own words? The issue you're dealing with here has to do with how **scoping** works, not closures per se. You could get the same behaviour by referencing a *global*, like if you removed `def nc():`. – wjandrea Jul 29 '22 at 04:24
  • BTW, welcome to Stack Overflow! Check out the [tour], and [ask] if you want tips. – wjandrea Jul 29 '22 at 04:24
  • @wjandrea: As per my understanding (of course, I may be wrong), a closure is a function that captures the local variables within the scope of its enclosing function so that it can make use of those variables after the enclosing variables go out of scope. Basically the enclosed function (closure) carries that state around. Am I wrong? – Siap Jul 29 '22 at 06:13
  • You're not wrong, but there's nothing in that definition that implies the closed-over variables are editable from the inner scope. – wjandrea Jul 29 '22 at 16:51

3 Answers3

1

Assigning to a name within a Python function makes it local by default, from the beginning of the function to the end. If you want to assign to names while having them remain tied to the closure scope, you must explicitly declare said name nonlocal:

def nc():
    i = 0
    def dummy():
        nonlocal i  # Makes code work as expected by forcing loads and stores to apply to
                    # closed-upon i
        i = i + 1
        return i
    return dummy
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Thanks for the explanation. If it were possible, I would have marked both yours and Ry's answers as accepted. – Siap Jul 29 '22 at 06:22
1

Python decides the scope of variables based on where it finds assignments to them. Because you have an assignment to i in the form i = ..., i will be a local variable scoped to dummy by default, but you can override that with a nonlocal statement:

def nc():
    i = 0
    def dummy():
        nonlocal i
        i = i + 1
        return i
    return dummy

x = nc()
print(x())

So yes, Python has closures.

Ry-
  • 218,210
  • 55
  • 464
  • 476
  • Thanks. That answers my query. I wasn't aware of the existance of nonlocal statement in Python. – Siap Jul 29 '22 at 06:09
0

Python supports closures. The issue you're dealing with here has to do with how scoping works, which is explained here: UnboundLocalError on local variable when reassigned after first use. Note that you can get the same error by referencing a global variable.

Here's an example closure:

def nc(i):
    def dummy():
        return i
    return dummy

x = nc(9)
i = 8
print(x())  # -> 9
wjandrea
  • 28,235
  • 9
  • 60
  • 81
  • I was specifically looking for a local variable of nc being captured by dummy. Wasn't aware of the nonlocal statement in Python. I was wondering why I was getting UnboundLocalError. – Siap Jul 29 '22 at 06:15
  • @Siap A parameter *is* a local variable. If you insert Ry's definition of `dummy`, you can see it works the same: `i` will increase each time you call `x`. But that's beside the point; I only provided this to show that closures do work in Python, plus that Python has lexical scoping. As for `nonlocal` and `UnboundLocalError`, those are explained in the link, so I'm not totally sure why you bring them up. – wjandrea Jul 29 '22 at 16:55
  • as mentioned, I was not aware of the existance of nonlocal. Hence my surprise on getting UnboundLocalError since I was expecting Python to see the variable i from the immediate outer scope (of dummy function) before concluding that the variable was not declared. Thus it turns out it was not really a closure related issue, (but rather my lack of understanding as to how Python handles scoping) as you are perhaps trying to point out. – Siap Aug 03 '22 at 08:13