Value in list changed after running through loop - python

I´ve been cleaning some data recently and there is just one thing that struck me.
Simple example:
test_list1 = [[1,2,3,4,5], [1,2,3,4,5]]
for x in test_list1:
for y in range(0, len(x)):
x[y] = 0
print(test_list1)
-> [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]
However, if I try the following, I obtain a different result:
test_list2 = [1,2,3,4,5]
for x in test_list2:
x = 0
print(test_list2)
-> [1, 2, 3, 4, 5]
It seems rather odd to me that in test_list1, I was able to change the values in its sub-lists without actually referring to test_list1.
Why did the values in test_list1 change just by running the loop if I didn´t explicitly stated that test_list1[0][0] = 0 and so on?
In test_list2, it was not possible.
Thanks in advance

Your question can be simplified to this:
a = 0
b = a
b = 1
print(a, b) # >> 0 1
a = [0,0]
b = a
b[0] = 1
print(a, b) # >> [1, 0] [1, 0]
That is because when you do b = a with numbers, b is a copy of a, so you can edit them separately. But when you do this with a list, the two variables correspond to the same object. This is mostly done to improve efficiency (copying a list every time you do something like a = b would be very inefficient and often useless). So when you edit one, the other one is affected.
In for x in test_list2:, x is a copy of an element of test_list2. But in for x in test_list1:, x directly correspond to an element (a list) of test_list2.
For more details, you can read this article about mutable objects.

I think the bottom line is that in your first loop, each instance of x is a list. In your second loop, each instance of x is an integer. In Python, lists are mutable, but ints are immutable. If you do a search online for "python mutable vs immutable", you can find a number of pages that describe what the difference is and why it leads to results like this.

Related

How to add a 1 into a list based on the values of another list, which give the position of where 1 should go

I have two lists:
a = [1,4,5]
x = [0,0,0,0,0,0,0,0,0,0]
I am stuck on making it so that it could look like this:
x= [0,1,0,0,1,1,0,0,0,0,0]
So that the number 1 is added to list x based on the value of list a, where the list values in a determine the position of where 1 should go. I am not sure if that makes much sense but it would help a lot.
You can use a for loop to iterate through list a and set the indexes of list x of its values to 1. So for example:
a = [1, 4, 5]
x = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
for index in a: # Iterating through the list a
x[index] = 1 # Setting the values to 1
print(x)
Alternative way,
using numpy fast and efficient
np_x = np.array(x)
np_x[a]=1

How do I merge a value and a range or list in python in a single line?

I am quite new to python, and still learning simple data handling in python.
I would like to combine 1 and range(3) to get a list [1,0,1,2]. What is best way to do this?
Is there any easy way like [1,0:3]?
Extended iterable unpacking, Python3.6+
>>> [1, *range(3)]
[1, 0, 1, 2]
With numpy, there's an even more convenient/concise expression using np.r_:
>>> import numpy as np
>>> np.r_[1,0:3]
array([1, 0, 1, 2])
This seems to be the most concise:
[1] + list(range(3))
# The following code should introduce you to lists, variables, for loops and the
basic interaction amongst all of them.
# Assign variable rng the upper limit of your range.The upper limit is not considered
in Python ranges of any kind
# hence the number should be one more than the number you want to consider/use.
rng = 3
# Initialize an empty list for use later.
lst = []
# Assign variable num the value you want to add and append it to the list
num = 1
lst.append(num)
# Print the current list.
print(lst)
# Use a simple for loop to iteratively add numbers in your range to the list.
for i in range (0,rng):
lst.append(i)
# Print the updated list.
print(lst)
#Output :
[1]
[1, 0, 1, 2]
you could just count from -1 and forget all about the minueses with the absolute value
[abs(i) for i in range(-1,3)]
output
[1, 0, 1, 2]
OR a genrator
map(abs,range(-1,3))
output
<map object at 0x0000026868B46278>
this is the same as the first output just as a genrator
AS A LIST
list(map(abs,range(-1,3)))
output
[1, 0, 1, 2]

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]

Problem with matrices in Python [duplicate]

This question already has answers here:
Multiply operator applied to list(data structure)
(2 answers)
Closed 9 years ago.
I was writing a programm in Python (2.5.4), and I realized that my code was not working because of something very unusual. I am going to give an example:
A = [[0]*2]*2
When I print A, I get:
[[0, 0], [0, 0]]
That is ok. But now I want to change the element in the first column and firts row. So I type:
A[0][0] = 1
But when I print A again, I get:
[[1, 0], [1, 0]]
However, I was expecting
[[1, 0], [0, 0]]
This is ruinning all my code. I want to know why this is happening and how I can fix it.
On the other hand, when I type:
B = [[0,0],[0,0]]
And make:
B[0][0] = 1
I get:
[[1, 0], [0, 0]]
This is even stranger! Aren't the two ways of implementing matrices equivalent? What if I wanted a 100x100 matrix with zeros? For this case, with a 2x2 matrix, I can type [[0, 0], [0, 0]]. But that is not a good solution.
This is because your list contains several references to a list.
>>> a = [0]
>>> l = [a,a]
>>> l[0][0] = "A"
>>> l
[['A'], ['A']]
We create a list and binded it to a. We then store two references to a in the list l via l=[a,a]. Then we manipulate one reference to a, and change it's first element to "A". Since a reference refers to a location in memory, my manipulating that reference (either element in l) we change the value in memory, hence affecting all other references to a.
This illustration, depicts the example above. The arrows represent a reference to a. They are the a's in l = [a,a]. When you change one of them, you change the value which they both point to. That interaction could be depicted like this:
We manipulate a via manipulating l[0] (l[0] is a reference to a), as such we can change the first element in a by changing l[0][0] (which would be the same as a[0]) to "A".
A depiction your list [[0]*2]*2 would look like this
"What if you wanted a 100 x 100 matrix of zeros?"
Use a list comprehension:
[[0] * 100 for x in range(100)]

Python: Change the parameter of the loop while the loop is running

I want to change a in the for-loop to [4,5,6].
This code just print: 1, 2, 3
a = [1,2,3]
for i in a:
global a
a = [4,5,6]
print i
I want the ouput 1, 4, 5, 6.
You'll need to clarify the question because there is no explanation of how you should derive the desired output 1, 4, 5, 6 when your input is [1, 2, 3]. The following produces the desired output, but it's completely ad-hoc and makes no sense:
i = 0
a = [1, 2, 3]
while i < len(a):
print(a[i])
if a[i] == 1:
a = [4, 5, 6]
i = 0 # edit - good catch larsmans
else:
i += 1
The main point is that you can't modify the parameters of a for loop while the loop is executing. From the python documentation:
It is not safe to modify the sequence being iterated over in the loop
(this can only happen for mutable sequence types, such as lists). If
you need to modify the list you are iterating over (for example, to
duplicate selected items) you must iterate over a copy.
Edit: if based on the comments you are trying to walk URLs, you need more complicated logic to do a depth-first or breadth-first walk than just replacing one list (the top-level links) with another list (links in the first page). In your example you completely lose track of pages 2 and 3 after diving into page 1.
The issue is that the assignment
a = [4,5,6]
just changes the variable a, not the underlying object. There are various ways you could deal with this; one would be to use a while loop like
a = [1,2,3]
i = 0
while i<len(a):
print a[i]
a = [4,5,6]
i += 1
prints
1
5
6
If you print id(a) at useful points in your code you'll realise why this doesn't work.
Even something like this does not work:
a = [1,2,3]
def change_a(new_val):
a = new_val
for i in a:
change_a([4,5,6])
print i
I don't think it is possible to do what you want. Break out of the current loop and start a new one with your new value of a.

Categories