Mutability, Locality, and Looping [duplicate] - python

This question already has answers here:
Local (?) variable referenced before assignment [duplicate]
(3 answers)
Closed 4 years ago.
I have some code from a beginner's coding exercise:
numbers = []
i = 0
def populate(maximum, step):
while i < maximum:
numbers.append(i)
i = i + step
populate(10, 2)
Which fails with the stack trace:
Traceback (most recent call last):
File "test_python.py", line 9, in <module>
populate(10, 2)
File "test_python.py", line 5, in populate
while i < maximum:
UnboundLocalError: local variable 'i' referenced before assignment
Here is my understanding of the problem so far...
i is an int and therefore it is immutable, and numbers is a list and is mutable
Since i is immutable, it can be read while it is out of scope. However, overwritting it while it is not in scope (ie. in the populate function), causes an UnboundLocalError
Since numbers is a mutable list, appending to it does not cause an overwrite and therefore does not cause an UnboundLocalError
If a simple change is made to the populate method, the program works successfully (due to i not being overwritten)
def populate(maximum, step):
new_i = i
while new_i < maximum:
numbers.append(i)
new_i = new_i + step
If I comment out the i = i + 1 line, the while loop (obviously) runs forever, but the program does not fail.
My question is then, why does Python fail when it hits the while loop on line 5, instead of the actual problem on line 7 (i = i + 1)? Is this some artifact of the interpreter taking the while loop as a block of code?
This piece of code fails in the correct spot:
def populate(maximum, step):
while i < maximum:
raise Exception("foo")
Stack trace:
Traceback (most recent call last):
File "test_python.py", line 12, in <module>
populate(10, 2)
File "test_python.py", line 6, in populate
raise Exception("foo")
Exception: foo
Another note: This only seems to be the case when the variable is used within the start of the control block (ie. while i < maximum). And the same behavior occurs for every type of control block: while, for, if, elif, etc.

Mutability is a red herring here. Mutable values are affected by scope the same way as immutable values. In fact, nothing in the Python language handles mutable values specially (this is a common myth, however).
The key insight is that the scope of a name is fixed in each scope. Within the scope of populate, every name must either be local or global: this decision is a part of the bytecode of the method.
A name that is only read can be looked up from an enclosing scope. But an assignment to a name anywhere within a scope forces that to be treated as a local variable everywhere in the scope. (Unless you use the global or nonlocal keywords.)
So if you're assigning to i anywhere in the method, then i must be a new variable local to the method, and not the global i. If you want i to mean the global i, simply add this line at the top of the method:
global i

Related

"local variable referenced before assignment" [duplicate]

This question already has answers here:
Assigning to variable from parent function: "Local variable referenced before assignment" [duplicate]
(5 answers)
Short description of the scoping rules?
(9 answers)
Closed 8 years ago.
I come from a land of heavy Java and PHP experience so when it comes to Python much of the rules do not make sense to me.
I have a recursive Fibonacci function below that spits out the error:
Traceback (most recent call last):
File "C:\Users\Nic\workspace\lab8\src\Main.py", line 26, in <module>
print fibcount(27),"took",calls,"calls."
File "C:\Users\Nic\workspace\lab8\src\Main.py", line 19, in fibcount
calls += 1
UnboundLocalError: local variable 'calls' referenced before assignment
Here is my code:
calls = 0
def fibcount(n):
calls += 1
if n < 2:
return (1,1)
f1,c1 = fibcount(n-1)
f2,c2 = fibcount(n-2)
return (f1+f2,c1+c2+1)
print fibcount(27),"took",calls,"calls."
In Java this would obviously work because calls is a global variable in respect to the function fibcount() so it confuses me that calls is somehow not in scope.
What am I doing wrong?
In Python, you need to declare global variables as being global inside functions which reassign them to a different value. You would use global for this:
def fibcount(n):
global calls
...
Otherwise, Python will treat the calls variable as if it were local.
Note however that you only need to do this for global variables which you reassign. Simply reading their values will work fine.
Also, reassigning global variables like this is considered ugly in Python and should be avoided whenever possible. Most of the time, you should stick to passing values to functions and then reassigning variables to their return values. If you need to maintain state, use a class.
The only time global variables are warranted in Python is when you want to have module-level constants. And in that case, the convention is that they should have all-caps names:
MYCONST = 12345

Python 2.7 supportability for appending to/extending empty variables within if else statements

I am currently initializing variables within if else statements by .extend(). At the end of my script I am writing the variables to an excel file using xlwt. However the compiler throws the following error:
Traceback (most recent call last):
File "C:\Python27\cascade.py", line 171, in <module>
NameError: name 'correct_screw_match' is not defined>
Currently I am also using extend as an error is thrown when using append.
for i in range (no_of_screw)
for iterator in range (0, read_limit, 2):
if (excel_screw_centroid[iterator]-25) <= screw_centroid[i,1] <= (excel_screw_centroid[iterator]+25):
if (excel_screw_centroid[iterator+1]-25) <= screw_centroid[i,0] <= (excel_screw_centroid[iterator+1]+25):
correct_screw_match.extend(i)
detected_a_screw = 1
else:
incorrect_screw_match.extend(i)
detected_a_screw = 0
I assume that the reason is, that the variables are declared within within an if else statement. This is why I initialized the variable as empty in the beginning of the script in the following way:
correct_screw_match = None
This however gives the following problem:
Traceback (most recent call last):
File "C:\Python27\cascade.py", line 106, in <module>
AttributeError: 'NoneType' object has no attribute 'extend'
The below to links explains in a simple manner, how to declare an empty variable, and the other how to append to it within if statements. I am however unaware of why this is not possible to me in the current way.
Reference to declaring empty variables.
Reference to append to empty lists.
It's often said that "Python doesn't have variables". That's not entirely correct, but what that pithy little phrase tries to represent is that all Python variables are simply names that refer to objects. When you say
x = 3
you are not declaring a variable named x and initializing it to 3; you are simply making the name x refer to an int object whose value is 3. Likewise,
y = x
does not create a new object whose value is taken from the value of x; it simply makes y a reference to the same object that x refers to.
It is not the variable that has a method extend, it is the object that the variable refers to. Also, objects have methods, not variables. In the line
correct_screw.extend(i)
correct_screw must first reference an object with an extend method before you can call the method. In this case, you want a list object, so you must first make correct_screw refer to such an object. It's not about declaring a variable, it's about making a name refer to a usable object.
correct_screw = list() # Using the type to create an empty list
or more concisely
correct_screw = [] # Using a list literal
Because names are created by assignment statements, and names themselves are not typed, you can ensure a name exists by assigning any value to it; the name on the left-hand side of an assignment need not (and indeed, usually does not) exist before. Any attempt to call a method using a name, though, requires that the name both exist and refer to an object of the correct type. This is why correct_screw = None does create the name correct_screw (so you don't get a NameError later), but does not make it refer to an object of the correct type for the subsequent call to extend.
Based on your usage and comments elsewhere, it seems you think that None and [] share a similar relation to undef and () in Perl, where they are somewhat interchangeable. However, Python is a strongly typed language, while Perl is not.
You should just initialise the variables (lists, presumably) with an empty value:
correct_screw_match = []
Then either use:
correct_screw_match.append(i)
...or if you must use extend:
correct_screw_match.extend([i])
It's not possible to you because you're not doing it. You are trying to call a list method on a value which is not a list, which of course won't work.
You need to set the variable to an empty list first, then call extend():
incorrect_screw_match = []
incorrect_screw_match.extend(i)
BUT, of course the above makes no sense at all, since it will clear the list every time a match is found.
Thus, the solution is to move the initialization to before the loops.

List append VS. += getting UnboundLocalError in inner function [duplicate]

This question already has an answer here:
Python: Difference between list.extend and list.__iadd__ [duplicate]
(1 answer)
Closed 4 years ago.
I've following code which raise Exception:
def a():
b = []
def inner():
b += 3
inner()
print (b)
>>> a()
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "<console>", line 6, in a
File "<console>", line 5, in inner
UnboundLocalError: local variable 'b' referenced before assignment
But, If I rewrite the code like this, it runs as expected:
def a():
b = []
def inner():
b.append(5)
inner()
print(b)
>>> a()
[5]
I'd like to know why this is happening, thanks.
The difference between the two is that b += 3 is an assignment -- it is assigning a totally new value (the result of addition of your empty list and 3) to b. b.append(), in contrast, mutates the list referenced by b without reassigning it.
inner() is accessing a variable in its parent's scope (I think this is not technically a closure since the execution of the parent has not completed). But inner() can only dereference that name, not assign to it, because b is not local to inner() and is not declared as global or nonlocal.
So you can dereference b and mutate the list it refers to, but you cannot assign to it. When you try to assign to it by starting the line with b += you are saying "treat b like a local". On a normal b = 3 assignment this would actually complete successfully, creating a local variable like any other. But in this case, since b has no already assigned value in the local context, dereferencing b in order to perform the addition procedure fails.
As it happens, you can't simply add an integer to a list in the first place. += is not the same thing as append even setting aside assignment vs. mutation, so even if you weren't reaching out of scope it would fail with a TypeError. b += [3] is closer to what you mean, although it will still fail due to the variable's scope.
Here you can find an exact explanation. http://eli.thegreenplace.net/2011/05/15/understanding-unboundlocalerror-in-python/
If you’re closely following the Python tag on StackOverflow, you’ll
notice that the same question comes up at least once a week.
<..>
Although this exact question is answered in Python’s official FAQ (right here), I
decided to write this article with the intent of giving a deeper explanation.
<..>
So where does the exception come from? Quoting the FAQ:
This is because when you make an assignment to a variable in a scope,
that variable becomes local to that scope and shadows any
similarly named variable in the outer scope.

Implementing Classes as dictionary in Python

I found below code at http://pydanny.com/python-dictionary-as-a-class.html. The code works fine and I kind of understand why it does but a similar code, below it gives error.
def newclass(**kwargs):
""" Use kwargs.update() method to handle inheritance """
def set(key, value):
""" Sets key/value to the kwargs.
Replicates self/this clumsily
"""
kwargs[key] = value
kwargs['set'] = set
return kwargs
My trial code:
def closing():
x=1
def closed():
print(x)
x=x+1
return(closed)
a=closing()
a()
Error Message:
Traceback (most recent call last):
File "<pyshell#606>", line 1, in <module>
a()
File "<pyshell#604>", line 4, in closed
print(x)
UnboundLocalError: local variable 'x' referenced before assignment
When I use 'nonlocal x' in the closed function it works but how come the initial code works without 'nonlocal'.
My understanding is that it's a closure and the inside function will keep reference of the outside (free) variable and whenever the inner function is called it will be able to act upon that closed variable but certainly I haven't understood some part of it properly.
Please help me clear the concept I am missing.
Thank you all the folks who answer. SO has been too helpful.
When I use 'nonlocal x' in the closed function it works but how come the initial code works without 'nonlocal'.
In the first snippet, you're mutating the existing kwargs value. You're not assigning a new value to it, or to any other name.
But in the second snippet, you're assigning a new value to x. That forces x to be local (unless you've said otherwise). Therefore, your x+1 is a reference to a local variable that hasn't been assigned yet. Hence the error.
For a more rigorous explanation, read Naming and binding in the documentation. The relevant bits are:
Each occurrence of a name in the program text refers to the binding of that name established in the innermost function block containing the use…
When a name is used in a code block, it is resolved using the nearest enclosing scope…
If a name is bound in a block, it is a local variable of that block, unless declared as nonlocal…
To learn how this works under the covers, you'll also need to read about functions and code objects in The standard type hierarchy. You probably don't need to know that part for understanding, but for debugging, you may want to inspect the __closure__ and other attributes.

UnboundLocalError in python confusing [duplicate]

This question already has answers here:
UnboundLocalError trying to use a variable (supposed to be global) that is (re)assigned (even after first use)
(14 answers)
Why does code like `str = str(...)` cause a TypeError, but only the second time?
(20 answers)
Closed 6 months ago.
Could anyone explain the exception the below code. It only works when I change the var sub in the display() to another name. There is no global variable sub as well. So what happened ?
def sub(a, b):
return a - b
def display():
sub = sub(2,1) // if change to sub1 or sth different to sub, it works
print sub
Any variable you assign to inside a scope is treated as a local variable (unless you declare it global, or, in python3, nonlocal), which means it is not looked up in the surrounding scopes.
A simplified example with the same error:
def a(): pass
def b(): a = a()
Now, consider the different scopes involved here:
The global namespace contains a and b.
The function a contains no local variables.
The function b contains an assignment to a - this means it is interpreted as a local variable and shadows the function a from the outer scope (in this case, the global scope). As a has not been defined inside of b before the call, it is an unbound local variable, hence the UnboundLocalError. This is exactly the same as if you had written this:
def b(): x = x()
The solution to this is simple: choose a different name for the result of the sub call.
It is important to note that the order of use and assignment makes no difference - the error would have still happened if you wrote the function like this:
def display():
value = sub(2,1) #UnboundLocalError here...
print value
sub = "someOtherValue" #because you assign a variable named `sub` here
This is because the list of local variables is generated when the python interpreter creates the function object.
This was originally a comment. The OP found this useful as an answer. Therefore, I am re-posting it as an answer
Initially, sub is a function. Then, it becomes the return value of a function. So when you say print sub, python doesn't know which sub you are referring to.
Edit:
First you define a function sub. Now, python knows what sub is.
When you create a variable and try to assign to it (say x = 2), python evaluates the stuff on the right hand side of the = and assigns the value of the evaluation as the value of the stuff on the left hand side of the =. Thus, everything on the right hand side should actually compute.
So if your statement was x = x+1, then x better have a value assigned to it before that line; and the previously defined x has to be of some type compatible with the addition of 1.
But suppose x is a function, and you make a variable called x in some other function, and try to assign to it, a value computed with function x, then this really starts to confuse python about which x you are referring to. This is really an oversimplification of this answer, which does a much better job of explaining variable scope and shadowing in python functions
For every variable used, Python determines whether it is a local or a nonlocal variable. Referencing a unknown variable marks it as nonlocal. Reusing the same name as a local variable later is considered a programmers mistake.
Consider this example:
def err():
print x # this line references x
x = 3 # this line creates a local variable x
err()
This gives you
Traceback (most recent call last):
File "asd.py", line 5, in <module>
err()
File "asd.py", line 2, in err
print x # this line references x
UnboundLocalError: local variable 'x' referenced before assignment
What happens is basically that Python keeps track of all references to names in code. When it reads the line print x Python knows that x is a variable from a outer scope (upvalue or global). However, in x = 3 the x is used as a local variable. As this is a inconsistency in the code, Python raises an UnboundLocalError to get the Programmers attention.
Python start executing your code and get the function first
def sub(a, b):
return a - b
So after executing this interpreter get the sub as a function. Now when come to next line it found
def display():
sub = sub(2,1) // if change to sub1 or sth different to sub, it works
print sub
so first line sub = sub (2, 1) will convert the sub function to sub variable. From this function you are returning the sub variable. So its create problem.

Categories

Resources