Local Variable Referenced before Assignment - Python - python

x = 2
y = 3
mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
print(mylist)
UnboundLocalError Traceback (most recent call
last) in
3
4
----> 5 mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
6
7 print(mylist)
in (.0)
3
4
----> 5 mylist = [ [x,y] for x in range(x+1) for y in range(y+1)]
6
7 print(mylist)
UnboundLocalError: local variable 'y' referenced before assignment
I do not understand what is wrong here and how to correct it.

This is because the CPython implementation of an inner comprehension in a multi-level comprehension is to make it a separate function, which has its own scope. By making y a left-value with its use as an iteration variable, y is determined to be a local variable to the separate function by the Python compiler, and as such the y in the range(y+1) cannot be referenced because the local y is not yet assigned a value in that function scope.
You can either rename the local y in the comprehension:
mylist = [[x, z] for x in range(x + 1) for z in range(y + 1)]
or use a non-comprehension solution instead:
mylist = []
for x in range(x + 1):
for y in range(y + 1):
mylist.append([x, y])

Normally, you see this error when you call a function that references a name in the local scope before assigning to it. As it happens, the outermost scope in a list comprehension works the same way in Python 3. This is a change from python 2, where loop variables in a comprehension would pollute the outer namespace.
In this particular case, using x and y as loop variables tells the interpreter that these are local variables. However, range(x + 1) and range(y + 1) attempt to read these variables before they are assigned. Python has an optimization to read variables that are determined to be local to a function (or comprehension) namespace from a special table rather than using normal LEGB lookup order. That is why you get the error instead of using the value from the outer scope.
A side-effect of this process is that the last value of x and y will not change the values that you assigned in the outer scope.
To fix this issue, ensure that the variables that define the range do not have the same name as the loop variables.

Try this:
x = 2
y = 3
mylist = [[j,k] for j in range(x+1) for k in range(y+1)]
print(mylist)
Prints out:
[[0, 0], [0, 1], [0, 2], [0, 3], [1, 0], [1, 1], [1, 2], [1, 3], [2, 0], [2, 1], [2, 2], [2, 3]]
As #Tim Roberts pointed out in his comment you are using x and y «for two conflicting purposes» in your list comprehension. You need to give unique names to each of them in the for loop, e.g. y -> k.
Have further further look at this SO post to learn more about nested list comprehensions.

Related

Query regarding call by value and call by reference in Python

I am quite confused, which case is correct, first case or second case or both the cases are correct in Python?
Why in first case the list x remain unchanged?
why in second case the list x also got modified?
Case 1:
x = [0, 1, 2]
def cleanIt(y):
y = []
return y
print(cleanIt(x)) #[]
print(x) #[0, 1, 2]
Case 2:
x = [0, 1, 2]
def appendOne(z):
z.append(4)
return z
print(appendOne(x)) #[0, 1, 2, 4]
print(x) #[0, 1, 2, 4]
Python behaves like pass-by-object-reference. Therefore what you really pass into the functions are actually references to the objects you give. In the first example, you pass a reference that points to the memory address in which x is present. However when you do the assignment operation y = [], you simply change the memory address your reference points to. You do not change the content of x. In the second example, you call the method append(). In that case, you call this method for the object that is present in the memory address pointed to by your reference.
These may come confusing at the beginning, but you should remember that assigning your variable directly to something else doesn't actually change your variable, it changes where your reference points to. However using a method, modifying your variable updates it.
I have used id() to give you a clear idea of the references. id() returns the memory address of the argument passed to it.
Case-1
x = [0, 1, 2]
print(id(x))
def cleanIt(y):
print(id(y))
y = []
print(id(y))
return y
print(cleanIt(x))
print(x)
Output:
139677941202304
139677941202304
139677940574528
[]
[0, 1, 2]
You pass the reference of x to cleanIt.
y = [] - Creates a new list altogether. This has nothing to do with x. See the references above.
return y - You return the newly created list i.e, y = []
Since x is unchanged, it will retain its initial values.
Case-2
x = [0, 1, 2]
print(id(x))
def appendOne(z):
print(id(z))
z.append(4)
print(id(z))
return z
print(appendOne(x))
print(x)
Output
139677940574912
139677940574912
139677940574912
[0, 1, 2, 4]
[0, 1, 2, 4]
You pass the reference of x to appendOne(). x and z point to the same list.
z.append(4) - Appends a 4 to list referenced by z.
Since z and x refer to the same list, changes made in z reflects in x too. See the references above.
In Python values are sent to functions by means of object reference.
Please read this to know more about this concept.
Edit:
If I want to have x unchanged while z should be [0,1,2,4], then how should I modify the python code?
This is how you can do.
x = [0, 1, 2]
print(id(x))
def appendOne(z):
print(id(z))
# Creates a new list with contents of x.
z = list(z)
z.append(4)
print(id(z))
return z
print(appendOne(x))
print(x)
Output
140368975642112
140368975642112
140368975011392
[0, 1, 2, 4]
[0, 1, 2]

I want to run multiple variables in a for loop in tandem and i want to take the sum of each iteration of x and y

I want to run multiple variables in a for loop together and I want to capture the values of all the variables at each iteration and sum them up. I'm going to take an example with two variables here:
for a,b in (range(a),range(b)):
print(a+b)
I'm totally new to this space and apologies if I'm not able to follow the standard practices of laying out a problem on this community.
You can use the built-in zip() method:
for a, b in zip(range(a), range(b)):
print(a + b)
Do note that iterating over a variable using the same name as the variable for the iteration variables will overwrite the original variable, like this:
x = [1, 2, 3]
y = [3, 2, 1]
for x, y in zip(x, y):
print(x + y)
print(x)
print(y)
Output:
4
4
4
3
1
You can use zip to couple two (or any number of) lists together.
a = [0,1,2]
b = [1,2,4]
for i, j in zip(a,b):
print(i+j)

Python Variable assignment in a for loop

I understand that in Python regular c++ style variable assignment is replaced by references to stuff ie
a=[1,2,3]
b=a
a.append(4)
print(b) #gives [1,2,3,4]
print(a) #gives [1,2,3,4]
but I'm still confused why an analogous situation with basic types eg. integers works differently?
a=1
b=a
a+=1
print(b) # gives 1
print(a) # gives 2
But wait, it gets even more confusing when we consider loops!
li=[1,2,3]
for x in li:
x+=1
print(li) #gives [1,2,3]
Which is what I expected, but what happens if we do:
a,b,c=1,2,3
li=[a,b,c]
for x in li:
x+=1
print(li) #gives [1,2,3]
Maybe my question should be how to loop over a list of integers and change them without map() as i need a if statement in there. The only thing I can come up short of using
for x in range(len(li)):
Do stuff to li[x]
is packaging the integers in one element list. But there must be a better way.
Well, you need to think of mutable and immutable type.
For a list, it's mutable.
For a integer, it's immutable, which means you will refer to a new object if you change it. When a+=1 is executed, a will be assigned a new object, but b is still refer to the same one.
a=[1,2,3]
b=a
a.append(4)
print(b) #[1,2,3,4]
print(a) #[1,2,3,4]
Here you are modifying the list. The list content changes, but the list identity remains.
a=1
b=a
a+=1
This, however, is a reassignment. You assign a different object to a.
Note that if you did a += [4] in the 1st example, you would have seen the same result. This comes from the fact that a += something is the same as a = a.__iadd__(something), with a fallback to a = a.__add__(something) if __iadd__() doesn't exist.
The difference is that __iadd__() tries to do its job "inplace", by modifying the object it works on and returning it. So a refers to the same as before. This only works with mutable objects such as lists.
On immutable objects such as ints __add__() is called. It returns a different object, which leads to a pointing to another object than before. There is no other choice, as ints are immutable.
a,b,c=1,2,3
li=[a,b,c]
for x in li:
x+=1
print(li) #[1,2,3]
Here x += 1 means the same as x = x + 1. It changes where x refers to, but not the list contents.
Maybe my question should be how to loop over a list of integers and change them without >map() as i need a if statement in there.
for i, x in enumerate(li):
li[i] = x + 1
assigns to every list position the old value + 1.
The important thing here are the variable names. They really are just keys to a dictionary. They are resolved at runtime, depending on the current scope.
Let's have a look what names you access in your code. The locals function helps us: It shows the names in the local scope (and their value). Here's your code, with some debugging output:
a = [1, 2, 3] # a is bound
print(locals())
for x in a: # a is read, and for each iteration x is bound
x = x + 3 # x is read, the value increased and then bound to x again
print(locals())
print(locals())
print(x)
(Note I expanded x += 3 to x = x + 3 to increase visibility for the name accesses - read and write.)
First, you bind the list [1, 2, 3]to the name a. Then, you iterate over the list. During each iteration, the value is bound to the name x in the current scope. Your assignment then assigns another value to x.
Here's the output
{'a': [1, 2, 3]}
{'a': [1, 2, 3], 'x': 4}
{'a': [1, 2, 3], 'x': 5}
{'a': [1, 2, 3], 'x': 6}
{'a': [1, 2, 3], 'x': 6}
6
At no point you're accessing a, the list, and thus will never modify it.
To fix your problem, I'd use the enumerate function to get the index along with the value and then access the list using the name a to change it.
for idx, x in enumerate(a):
a[idx] = x + 3
print(a)
Output:
[4, 5, 6]
Note you might want to wrap those examples in a function, to avoid the cluttered global namespace.
For more about scopes, read the chapter in the Python tutorial. To further investigate that, use the globals function to see the names of the global namespace. (Not to be confused with the global keyword, note the missing 's'.)
Have fun!
For a C++-head it easiest tho think that every Python object is a pointer. When you write a = [1, 2, 3] you essentially write List * a = new List(1, 2, 3). When you write a = b, you essentially write List * b = a.
But when you take out actual items from the lists, these items happen to be numbers. Numbers are immutable; holding a pointer to an immutable object is about as good as holding this object by value.
So your for x in a: x += 1 is essentially
for (int x, it = a.iterator(); it->hasMore(); x=it.next()) {
x+=1; // the generated sum is silently discarded
}
which obviously has no effect.
If list elements were mutable objects you could mutate them exactly the way you wrote. See:
a = [[1], [2], [3]] # list of lists
for x in a: # x iterates over each sub-list
x.append(10)
print a # prints [[1, 10], [2, 10], [3, 10]]
But unless you have a compelling reason (e.g. a list of millions of objects under heavy memory load) you are better off making a copy of the list, applying a transformation and optionally a filter. This is easily done with a list comprehension:
a = [1, 2, 3, 0]
b = [n + 1 for n in a] # [2, 3, 4, 1]
c = [n * 10 for n in a if n < 3] # [10, 20, 0]
Either that, or you can write an explicit loop that creates another list:
source = [1, 2, 3]
target = []
for n in source:
n1 = <many lines of code involving n>
target.append(n1)
Your question has multiple parts, so it's going to be hard for one answer to cover all of them. glglgl has done a great job on most of it, but your final question is still unexplained:
Maybe my question should be how to loop over a list of integers and change them without map() as i need a if statement in there
"I need an if statement in there" doesn't mean you can't use map.
First, if you want the if to select which values you want to keep, map has a good friend named filter that does exactly that. For example, to keep only the odd numbers, but add one to each of them, you could do this:
>>> a = [1, 2, 3, 4, 5]
>>> b = []
>>> for x in a:
... if x%2:
... b.append(x+1)
Or just this:
>>> b = map(lambda x: x+1, filter(lambda x: x%2, a))
If, on the other hand, you want the if to control the expression itself—e.g., to add 1 to the odd numbers but leave the even ones alone, you can use an if expression the same way you'd use an if statement:
>>> for x in a:
... if x%2:
... b.append(x+1)
... else:
... b.append(x)
>>> b = map(lambda x: x+1 if x%2 else x, a)
Second, comprehensions are basically equivalent to map and filter, but with expressions instead of functions. If your expression would just be "call this function", then use map or filter. If your function would just be a lambda to "evaluate this expression", then use a comprehension. The above two examples get more readable this way:
>>> b = [x+1 for x in a if x%2]
>>> b = [x+1 if x%2 else x for x in a]
You can do something like this: li = [x+1 for x in li]

Why is this happening in Python ? Is it do with list mutability?

Please don't be harsh if my question is very simple or obvious.
Am a Python newbie, so just started out.
Actually this was a piece of code I came across on this Stack Overflow only but could not find an answer for why this is happening, so decided to ask it myself.
I wrote the below two programs :
1)
x=[1,2,3]
y=x
print x
y=y+[3,2,1]
print x
Output:
[1,2,3]
[1,2,3]
2)
x=[1,2,3]
y=x
print x
y+=[3,2,1]
print x
Output:
[1,2,3]
[1,2,3,3,2,1]
I can't understand why the two outputs are different in this case ?
is y=y+(something) not the same as y+=(something)
what is it that I am missing here ?
Thanks a lot for helping me out on this
This has nothing to do with mutability. It has to do with list objects and references to it.
When you do:
y=x
You are essentially making y refer to the same list that x is referring to.
Now,
y=y+[3,2,1]
^-------^ - Create a **new** List is that is equal to concatenation of `y` and [1,2,3]
^---^ - Bind variable `y` to this **new** list.
- The original list which `x` refered to -- is still *intact*
Again,
y+=[3,2,1]
^-------^ - Append [1,2,3] **in-place**.
- Since even `x` is pointing to the same list, it gets modified.
y = y+something will replace the contents of y, while y+=something will modify the list in-place.
TL;DR;
Code explanation:
>>> x = [1, 2]
>>> y = x
>>> id(y) == id(x)
True
>>> y += [3]
>>> id(y) == id(x)
True
>>> y = y + [3]
>>> id(y) == id(x)
False
Here's a little explanation of what's going on in your code.\
First code
You declared x:
x=[1,2,3]
Then you point y to the value of x:
y=x
After that, here's the tricky part:
y=y+[3,2,1]
This creates a new y variable, replacing the old one. y is no longer related to x. Now, x has it's own place in memory, and y has a separate one
Second code
Declare x:
x=[1,2,3]
Points y to the value of x:
y=x
And lastly:
y+=[3,2,1]
This modifies the list pointed by y in-place. In other words, you're modifying x as well:
If this isn't clear enough, just comment:)
Hope this helps!
After the line
y = x
Both x and y point to the same object. In way that operators are overloadable in Python:
y += [3, 2, 1]
Is calling function
y.__iadd__([3, 2, 1])
Which in turn adds the elements of [3, 2, 1] to a list pointed by y - which happens to be the same list pointed by x.
On the other hand:
y = y + [3, 2, 1]
Is the same as:
y = y.__add__([3, 2, 1])
Which, in turn, creates a new list containing combination of both and assign it to y. After it y and x points to different objects and the one pointed by x is not modified.
No, it's not the same. In fact this was one of the reasons why it took a long time for Python to get +=, in the beginning people thought it would be too confusing to add.
y = y + [3, 2, 1]
Rebinds y to a new value, the one that is the result of the expression y + [3, 2, 1].
But
y += [3, 2, 1]
changes y, if it currently refers to something that can be changed. And lists can.
Since the list that y refers to is changed, and x refers to the same list, printing x also shows the new value.
If y were immutable (like a tuple), then it doesn't work that way:
x = y = (1, 2, 3)
y += (3, 2, 1)
print(x) # prints (1, 2, 3)
print(y) # prints (1, 2, 3, 3, 2, 1)
The reason is that in the case of mutable objects, it's more likely that changing the object is what you actually want, so they made it work that way. Immutable object can't work that way, so they don't.
In python every time you use the operator = you "delete" the old version and create a new one.
Thus y = y + something will create a new variable of y that isn't the same as the old y.
While y+=something will add something to the current value in y.

Python list reference in a function.

I'm having trouble understanding the odd behaviour in python functions if i pass in a list.
I made the following functions:
def func(x):
y = [4, 5, 6]
x = y
def funcsecond(x):
y = [4, 5, 6]
x[1] = y[1]
x = [1, 2, 3]
When i call func(x) and then print out x , it prints out [1, 2, 3], just the way x was before, it doesnt assign the list y to x. However, if i call funcsecond(x), it assigns 5 to the second position of x. Why is that so? When i assign the whole list, it doesn't do anything, but when i assign only one element, it changes the list i originally called it with.
Thank you very much and i hope you understand what im intending to say , i'm having a hard time expressing myself in English.
The former rebinds the name, the latter mutates the object. Changes to the name only exist in local scope, whereas a mutated object remains mutated after the scope is exited.
this is happening beacuse x points to a object which is mutable.
def func(x): # here x is a local variable which refers to the object[1,2,3]
y = [4, 5, 6]
x = y #now the local variable x refers to the object [4,5,6]
def funcsecond(x): # here x is a local variable which refers to the object[1,2,3]
y = [4, 5, 6]
x[1] = y[1] # it means [1,2,3][1]=5 , means you changed the object x was pointing to
x = [1, 2, 3]

Categories