Slice nested lists [duplicate] - python

This question already has answers here:
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
Closed 6 years ago.
It is quite a silly question, but I am really confused.
Please have a look my code:
>>> my_list = [1, 2, 3]
>>> my_list_new = my_list[:]
>>> my_list_new[0] = 100
>>> my_list_new
[100, 2, 3]
>>> my_list
[1, 2, 3]
So it works as it should. I copied my_list. When I changed the my_list_new - only one list changed.
Now look here:
>>> my_list2 = [[1, 2, 3], [4, 5, 6]]
>>> my_list_new2 = my_list2[:]
>>> my_list_new2[0][0] = 100
>>> my_list_new2
[[100, 2, 3], [4, 5, 6]]
>>> my_list2
[[100, 2, 3], [4, 5, 6]]
As you can see I changed my_list_new2, but both lists changed. Is it normal Python behaviour for nested lists? How to avoid it?

This should work as intended
my_list_new2 = [list[:] for list in my_list2]
Proof
my_list2 = [[1, 2, 3], [4, 5, 6]]
my_list_new2 = [list[:] for list in my_list2]
my_list_new2[0][0] = 100
my_list_new2
[[100, 2, 3], [4, 5, 6]]
my_list2
[[1, 2, 3], [4, 5, 6]]

List are mutable in python. Meaning:
a = [1,2,3]
b = a
a[0] = 4
print(b)
Will print [4, 2, 3]
So in case of nested lists you only copy the "outer" list, and modify the inner list, so you will modify the other list as well. If you are interested in this look up list pointers in python
Solution:
my_list2 = [[1, 2, 3], [4, 5, 6]]
my_list_new2 = [inner_lst.copy() for inner_lst in my_list2] # You can use inner_lst[:] as well
my_list_new2[0][0] = 100
print(my_list2)
Returns: [[1, 2, 3], [4, 5, 6]]

A list of lists (in your case l1 = [[1, 2, 3], [4, 5, 6]]) contains objects (list).
A slice of a list is a new shallow copy of that subset of the list where it inserts references to the objects. It can be seen from the following snippet code.
l1 = [[1, 2, 3], [4, 5, 6]]
l2 = l1[:]
print(id(l1)) # 4406414512
print(id(l2)) # 4382080248
# Shallow copy, insert references
print(id(l1[0])) # 4439177768
print(id(l2[0])) # 4439177768
To copy everything, use copy.deepcopy where it inserts copies of the objects. It can be seen from,
import copy
l1 = [[1, 2, 3], [4, 5, 6]]
l2 = copy.deepcopy(l1)
l2[0][0] = 100
print(l1) # [[1, 2, 3], [4, 5, 6]]
print(l2) # [[100, 2, 3], [4, 5, 6]]

Your outer list my_list2 contains references to two other lists.
Even if you make an independent copy the outer list my_list2, it will still (initially) contain the same items, i.e. the same references to the same two lists. (If you're familiar with C/C++, you can loosely think of the references-to-lists as pointers.) That's called a "shallow copy". Here are a few ways of creating a shallow copy:
my_new_list2 = list(my_list2)
my_new_list2 = [item for item in my_list2]
my_new_list2 = my_list2[:]
import copy; my_new_list2 = copy.copy(my_list2)
To get the behaviour you want, you will need to duplicate the inner lists too. You can do that by hand, as others have suggested—or you can let the Python standard library do it automatically. That's what copy.deepcopy is for:
import copy
my_list_new2 = copy.deepcopy(my_list2)
The advantage of deepcopy is that it will work its way down an arbitrary number of levels of nesting, not necessarily just the first inner level.

Related

Python casting leads to weird mutability result

Why is it that when I write the code block as:
def rev(li):
li.reverse()
l = [[1, 2, 3], [4, 5, 6]]
map(rev, l)
print(l)
The resulting list is the same as the original list, but when I rewrite the code to now cast the map object into a list as such:
def rev(li):
li.reverse()
l = [[1, 2, 3], [4, 5, 6]]
list(map(rev, l))
print(l)
It does reverse the inner lists to give me [[3, 2, 1], [6, 5, 4]]. How does casting the map object to a list change mutability rules for the list l?

Python List inserting independent reference variables [duplicate]

This question already has answers here:
How to deep copy a list?
(10 answers)
How do I clone a list so that it doesn't change unexpectedly after assignment?
(24 answers)
What is the best way to copy a list? [duplicate]
(7 answers)
Closed 1 year ago.
Let's assume that a is some reference variable and I want to put it into a list three times:
a = [1,2,3]
b = [a]*3
>>> b
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
So this still works as expected. But if I append an element to one of the reference variables it is reflected to all of them:
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
Does this happen because the * operator in the first code block only copies the references and the list only points to a? Do I need a for-loop in order to be able to have a list of independent reference variables (copies) n times? (where n is 3 in the example above)
How excactly does the * operator work in this instance?
With a for-loop and the append()-Method the behaviour is the same - are references also used here?:
>>> a = [1,2,3]
>>> b = []
>>> for i in range(2):
... b.append(a)
...
>>> b
[[1, 2, 3], [1, 2, 3]]
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3, 4]]
When inserting copies the behaviour is as expected:
>>> a = [1,2,3]
>>> b = []
>>> for i in range(2):
... b.append(a.copy())
...
>>> b
[[1, 2, 3], [1, 2, 3]]
>>> b[0].append(4)
>>> b
[[1, 2, 3, 4], [1, 2, 3]]
Is there a shorter, more convenient way to do this than to loop with .append() and .copy() as I did above?
My Python-Version is Python 3.8.6
Note that my questions go beyond just using deep_copy() as it is about inserting elements more often in a list and the *-operator - I know what deep_copy() is!

Quicker way to join multiple lists passed as args to a Python function?

So I have a function which takes a variable number of lists as an argument, then combines those lists into one single list:
def comb_lists(*lists):
sublist = []
for l in lists:
sublist.extend(l)
print(sublist)
>>> comb_lists([1, 2], [3, 4], [5, 6])
[1, 2, 3, 4, 5, 6]
And it works. But I was just wondering if there was a simpler solution? I tried a list comprehension using list unpacking, but that returned a SyntaxError:
def comb_lists(*lists):
sublist = [*l for l in lists]
>>> comb_lists([1, 2], [3, 4], [5, 6])
SyntaxError: iterable unpacking cannot be used in comprehension
Is there any neater or quicker way to do this?
EDIT: itertools looks really useful for this sort of thing. I'd be interested to know if there's any way of doing it that doesn't rely on imports though.
here is the simplest solution
result = sum(lists, [])
There's built-in function chain.form_iterable() in itertools module to do this:
>>> from itertools import chain
>>> my_list = [[1, 2], [3, 4], [5, 6]]
>>> list(chain.from_iterable(my_list))
[1, 2, 3, 4, 5, 6]
If you do not want to import any module, you can write nested list comprehension to achieve this as:
>>> my_list = [[1, 2], [3, 4], [5, 6]]
>>> [e for l in my_list for e in l]
[1, 2, 3, 4, 5, 6]

Python function replacing input variable

I'm finding some weird behavior in python that I can't explain. I've written the following function:
def process_data(data_in, unique_recs_in):
recs = unique_recs_in
for x, dat in enumerate(recs):
recs[x].append(data_in.count(dat))
return recs
where data_in and unique_recs_in are lists of lists. 'data_in' counts represents receptors, with a list being stored for each receptor each time in fails a critera. 'Unique_recs_in' is a list of all the unique receptor locations.
What I can't figure out is when I call this function, my output 'recs' returns properly. However, 'unique_recs_in' changes when I run the function and is identical to 'recs'. I have bug tested the code and can confirm that it's in this function that that happens. Anyone have any ideas?
Edit: sample input below
data_in
[['631507.40000', '4833767.20000', '60.00'], ['631507.40000', '4833767.20000', '63.00'], ['631507.40000', '4833767.20000', '66.00']]
unique_recs_in:
[['631552.90000', '4833781.00000', '24.00'], ['631569.50000', '4833798.80000', '48.00'], ['631589.20000', '4833745.50000', '12.00']]
recs = unique_recs_in simply creates a new reference to the list object, to get a completely new copy of a list of lists use copy.deepcopy.
>>> lis = [[1, 2], [3, 4]]
>>> a = lis
>>> a.append(4) #changes both `a` and `lis`
>>> a, lis
([[1, 2], [3, 4], 4], [[1, 2], [3, 4], 4])
Even a shallow copy is not enough for list of lists:
>>> a = lis[:]
>>> a[0].append(100) #Inner lists are still same object, just the outer list has changed.
>>> a, lis
([[1, 2, 100], [3, 4]], [[1, 2, 100], [3, 4]])
copy.deepcopy returns a completely new copy:
>>> import copy
>>> a = copy.deepcopy(lis)
>>> lis
[[1, 2, 100], [3, 4], 4]
>>> a.append(999)
>>> a, lis
([[1, 2, 100], [3, 4], 4, 999], [[1, 2, 100], [3, 4], 4])
>>> a[0].append(1000)
>>> a, lis
([[1, 2, 100, 1000], [3, 4], 4, 999], [[1, 2, 100], [3, 4], 4])
If the list contains only immutable objects, then only a shallow copy is enough:
recs = unique_recs_in[:]
You might find this helpful as well: Python list([]) and []
You will probably want to store in recs a copy of unique_recs_in so it wont be modified. Try with this:
recs = [l[:] for l in unique_recs_in] # although Ashwini `deepcopy` is more elegant
By assigning a list to another list l1=l2 you're just establishing and alias between them (ie: both variables reference to the same list in memory) so modifying one will modify the other (because they are the same).
Hope this helps!

Learning loops and lists

I'm a Python beginner and I'm currently going through Zed Shaw's course "Learn Python the Hardway"
So, in exercise 32 we are told:
How do you make a 2-dimensional (2D) list?
That's a list in a list like this: [[1,2,3],[4,5,6]]
I did this:
# Extra 1
global_list = [[1, 2, 3]]
inside_list = []
for i in global_list[0]:
inside_list.append(i)
global_list.append(inside_list)
print(global_list)
But I’m not entirely convinced that's the correct way. My question is: Is there a way to get the same result without ever leaving the for i in.... loop?
I also tried this, to no avail.
global_list = [[1, 2, 3]]
inside_list = []
for i in global_list[0]:
inside_list.append(i)
global_list.append(inside_list)
print(global_list)
Thanks in advance for your answers.
Lists can be appended and inserted into a list just like any other object, e.g:
outer_list = []
print(outer_list)
inner_list1 = [1, 2, 3]
outer_list.append(inner_list1)
print(outer_list)
inner_list2 = [4, 5, 6]
outer_list.append(inner_list2)
print(outer_list)
I am not sure if you already went over list comprehension. However, one nice way of doing what you are doing is:
>>> global_list = [[1,2,3]]
>>> global_list.append([i + 3 for i in global_list[0]])
>>> print global_list
[[1, 2, 4], [4, 5, 6]]
The question was "How do you make a 2-dimensional (2D) list?". The answer given was "That's a list in a list like this: [[1,2,3],[4,5,6]]". Literally, that's the answer:
>>> a = [[1, 2, 3], [4, 5, 6]]
>>> print a
[[1, 2, 3], [4, 5, 6]]
You can also do this:
>>> a = [[1, 2, 3]]
>>> a.append([4, 5, 6])
>>> a
[[1, 2, 3], [4, 5, 6]]
You don't need a for loop to append a list inside another list:
>>> a = [[1, 2, 3]]
>>> a[0]
[1, 2, 3]
>>> a.append(a[0])
>>> a
[[1, 2, 3], [1, 2, 3]]
However, this makes the second element of the list of lists the same as the first, so if you change one you change the other:
>>> a[0] is a[1]
True
>>> a[0][0] = 4
>>> a
[[4, 2, 3], [4, 2, 3]]
What you can do to make a copy of the list is list(a[0]):
>>> a = [[1, 2, 3]]
>>> a[0]
[1, 2, 3]
>>> a[0] is a[0]
True
>>> list(a[0])
[1, 2, 3]
>>> a[0] is list(a[0])
False
>>> a.append(list(a[0]))
>>> a
[[1, 2, 3], [1, 2, 3]]
>>> a[0] is a[1]
False
>>> a[0][0] = 4
>>> a
[[4, 2, 3], [1, 2, 3]]

Categories

Resources