This bit of Python does not work:
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start
start += 1
return closure
x = make_incrementer(100)
iter = x()
print iter.next() # Exception: UnboundLocalError: local variable 'start' referenced before assignment
I know how to fix that error, but bear with me:
This code works fine:
def test(start):
def closure():
return start
return closure
x = test(999)
print x() # prints 999
Why can I read the start variable inside a closure but not write to it?
What language rule is causing this handling of the start variable?
Update: I found this SO post relevant (the answer more than the question): Read/Write Python Closures
Whenever you assign a variable inside of a function it will be a local variable for that function. The line start += 1 is assigning a new value to start, so start is a local variable. Since a local variable start exists the function will not attempt to look in the global scope for start when you first try to access it, hence the error you are seeing.
In 3.x your code example will work if you use the nonlocal keyword:
def make_incrementer(start):
def closure():
nonlocal start
while True:
yield start
start += 1
return closure
On 2.x you can often get around similar issues by using the global keyword, but that does not work here because start is not a global variable.
In this scenario you can either do something like what you suggested (x = start), or use a mutable variable where you modify and yield an internal value.
def make_incrementer(start):
start = [start]
def closure():
while True:
yield start[0]
start[0] += 1
return closure
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.
Example
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
x = make_incrementer([100])
iter = x()
print iter.next()
In Python 3.x you can use the nonlocal keyword to rebind names not in the local scope. In 2.x your only options are modifying (or mutating) the closure variables, adding instance variables to the inner function, or (as you don't want to do) creating a local variable...
# modifying --> call like x = make_incrementer([100])
def make_incrementer(start):
def closure():
# I know I could write 'x = start' and use x - that's not my point though (:
while True:
yield start[0]
start[0] += 1
return closure
# adding instance variables --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure():
while True:
yield closure.start
closure.start += 1
closure.start = start
return closure
# creating local variable --> call like x = make_incrementer(100)
def make_incrementer(start):
def closure(start=start):
while True:
yield start
start += 1
return closure
Related
class Solution:
def diameterOfBinaryTree(self, root: Optional[TreeNode]) -> int:
res = [0]
def dfs(root):
if not root:
return -1
left = dfs(root.left)
right = dfs(root.right)
res[0] = max(res[0], 2 + left + right)
return 1 + max(left, right)
dfs(root)
return res[0]
Why can't we access res (if its a variable) inside that function?
I suspect you are getting "local variable referenced before assignment". If so, this is because you are using nonlocal variables and python is deciding some of your code is making that variable name local. The short story is, make the variables nonlocal explicitly using "nonlocal" function. The answer why arrays work and variables dont is that you can add elements to an array without changing the array. Here is some sample code that shows what works and what doesn't. It can behave in very non obvious ways, since "a += [3]" assigns a new array when "a.append(3)" doesn't. which has a big impact in this scenario. But use the nonlocal keyword and you will get the exact behavior you expect.
#!/usr/bin/python
def foo():
var = "a"
array = [0]
# will work
def test1():
print(var)
# will break, becase var is local, even thought assigned happens later
def test2():
print(var)
var = "b"
# works because we told python var is nonlocal
def test3():
nonlocal var
print(var)
var = "b"
# breaks because we are actually assigning the array
def test4():
print(array[0])
array += [3]
# works becase array is not local
def test5():
nonlocal array
print(array[0])
array += [3]
# works because we are not assiging the array
def test6():
print(array[0])
array.append(3)
# works becasuse we are not assigning the arry
def test7():
print(array[0])
array[0] = [3]
test7()
foo()
I have to execute the following code wherein I will be calling the function main again and again.
so here as I need to use i = i+1, I need to declare and initialize i in the first place right, but when i call the main function it again defines i=0 and the whole purpose of i = i+1 is lost.
How can I solve this error?
I have given the condition just as an example.
Basically what I want is i should be initialized only once, inspite of how many number of times main is called.
def main():
i = 0
if 0<1:
i = i+1
y = i
There are a couple ways to do this that don't involve globals. One is capture the value of i in a closure and return a new function that increments this. You will need to call the initial function once to get the returned function:
def main():
i = 0
def inner():
nonlocal i
i += 1
return i
return inner
f = main()
f()
# 1
f()
# 2
You can also create a generator which is a more pythonic way to do this. The generator can be iterated over (although use caution since it iterates forever) or you can get a single value by passing it to next():
def main():
i = 1
while True:
yield i
i += 1
f = main()
next(f)
# 1
next(f)
# 2
You can also use itertools.count
So you haven't declared i as a global variable
Do something like this
global i
i = 0
def main():
if 0<1:
global i
i = i+1
y = i
The reason behind this is because inside a function all the variables are local meaning they only exist inside the function while the function is called, so if you want a function to be able to change a variable for the whole code, you'll need to announce it as a global so python knows to change the value of it for the entire code
I'm not sure exactly what you are trying to do, but I believe there is an easier way to do whatever it is you are doing
It looks like you want to maintain state in a function call which is a good reason to convert it to a class.
class MyClass:
def __init__(self):
self.i = 0
def main(self):
self.i += 1
y = self.i
myclass = MyClass()
myclass.main()
myclass.main()
print(myclass.i)
My example below. I have tried adding global to the variable that I want to change but doesn't help. The error I'm getting is in the comment.
def getNumOfSwapsToSort(array):
sorted = array.copy()
sorted.sort()
swaps = 0
def swap():
currentSwaps = 0
for i, el in enumerate(array):
if ((len(array) - 1) == i) and currentSwaps != 0:
swap()
elif el != sorted[i]:
idx = sorted.index(el)
temp = array[idx]
array[idx] = el
array[i] = temp
# UnboundLocalError: local variable 'swaps' referenced before assignment
# if I use global swaps, then NameError: name 'swaps' is not defined
swaps += 1
currentSwaps += 1
swap()
return swaps
The swaps variable in your code is not global, it's nonlocal. It was defined in the enclosing function getNumOfSwapsToSort(), not in the global namespace.
Your other function, swap(), has access to this nonlocal variable, but the variable is protected from being edited from the inner function. If you try to edit or overwrite it, it's going to be treated as a new, local variable - hence the UnboundLocalError you're getting.
Try using the nonlocal statement:
nonlocal swaps
swaps += 1
Here's an article with a few simple examples which explain the concept of closure in Python.
swaps isn't a global, it's in getNumOfSwapsToSort, which is the parent scope of swap. So use a nonlocal declaration.
def swap():
nonlocal swaps
...
How do I make code like the following work? I want to reference a variable, for assignment, in the enclosing function scope.
def outer():
x = 0
def inner():
x += 1
inner()
The code as written gives an UnboundLocalError. I understand why I get this error, I just don't know how I indicate that x comes from the wrapping scope.
You can do:
def outer():
x = [0]
def inner():
x[0] += 1
inner()
You can't rebind a non-local, but you can mutate it.
You cannot do what you ask in a clean way. There is nothing analagous to the global statement that can help you. You'll want to code it like this:
def outer():
x = 0
def inner(x):
return x + 1
x = inner(x)
This has the added advantage of making it explicitly clear as to how data passes into, and out of, the function.
Perhaps you will need to replace x with an object whose state can be mutated.
I have this code:
def r():
i += 1
return i
def f():
return x*a
i = 0
a=2
x=3
print f()
print r()
I get this error for r(), but not for f():
~$ python ~/dev/python/inf1100/test.py
6
Traceback (most recent call last):
File "/home/marius/dev/python/inf1100/test.py", line 18, in <module>
print r()
File "/home/marius/dev/python/inf1100/test.py", line 2, in r
i += 1
UnboundLocalError: local variable 'i' referenced before assignment
Why can f() use variables defined outside of the function, whilst r() cannot?
That's because r reassigns the global variable i. f on the other hand just uses it. Remember that i += 1 is the same as i = i + 1.
Unless you explicitly tell it otherwise, Python treats all variables used within a function as being local. Furthermore, since there is no variable i defined within the local scope of r, it throws the error.
If you want to reassign a global variable within a function, you have to put:
global var
at the top of your function to explicitly declare var to be global.
So, to make r work, it should be rewritten to this:
def r():
global i
i += 1
return i
I'd like to point out that most of the time, this:
x = 1
def f():
global x
x += 1
f()
is bad practice, and you want to use parameters instead:
x = 1
def f(a_number):
return a_number + 1
x = f(x)
Also, here:
def r():
global i
i += 1
return i
return i is redundant, the variable is increased by the calling of the function.
Also this part of the Python FAQ is relevant and useful.
This piece:
def r():
i += 1
return i
not only uses global variables, but also tries to modify them (or more accurately: assign different value to global variable i).
To make it work, you can just declare this variable as global:
def r():
global i
i += 1
return i
In r you are shadowing your global i. Since it is not assigned before you attempt to add to it, you get an error.
A possible solution is to use global i in the r function like so
def r():
global i
i += 1
return i
If you assign into a variable, python will consider the variable local and won't bother looking for a global variable of the same name. Use global as suggested in other answers.