What kind of copy is slice operation a[:]? - python

I leave python for a while and now I'm preparing for interviews. When I was reviewing some basics I found this:
>>> a = [1,2]
>>> b = a
>>> b.append(3)
>>> a
[1, 2, 3]
>>> a = [1,2]
>>> a[:] = [1,2,3]
>>> a
[1, 2, 3]
>>> a = [1,2]
>>> b = a[:]
>>> b.append(3) # /a[:].append(3)
>>> a
[1, 2]
As I understand it, in the second case a[:] acts as a shallow copy while in the third it is a deep copy. Can anyone help me with this basic concept, thanks beforehand.

No, in the second case, the expression a[:] on the left-hand side of the assignment does not make any copy (shallow or deep) of a
It just says that you want to replace the complete slice of a, with the values from [1, 2, 3]
Only in the 3rd part of your code snippet, a[:] results in a copy of a. And it will be a shallow copy -- not a deep one. But to demonstrate that a[:] makes only a shallow copy, you'll have to fill your list a with some mutable objects. Currently, you've only filled them with int values. For example, you could fill your list a with inner lists:
a = [ ['a', 'b'], 2] # First element of a is a list, which is a mutable object.
b = a[:] # b will now have a shallow copy of a, which means that
# the first element of a and the first element of b, both refer to
# the same object, which is the inner list ['a', 'b']
b[0].append('c') # Mutate the first element of b.
a # You'll find that the change is visible thru list a also.
Output:
[['a', 'b', 'c'], 2]

It is a shallow copy. But I think you misunderstand what "shallow" means. Your 3rd example does only illustrate that a and b are different objects. However, the items they contain are not different objects. You can't see it in this case, since you have numbers, which are immutable anyway.
However, if the items in a were mutable objects (eg, other lists), modifying them in a would also modify them in b
a = [[]]
b = a[:]
b.append(3) # this does not change a
a[0].append(1) # this changes b
print(b)

a=[1,2]
b=a #in this case whatever changes we apply to a or b
is reflected in both.
a.append(3)
b
[1, 2, 3]
a
[1, 2, 3]
b.append(4)
a
[1, 2, 3, 4]
b
[1, 2, 3, 4]
a=[[]]
b=a[:] #in this case whatever changes we apply to
b is reflected only in b and whatever changes are
applied in a is only reflected in a.
b.append(3)
a[0].append(1)
print(b)
[[1], 3]
a
[[1]]
a=[1,2]
b=a[:]
b.append(3)
b
[1, 2, 3] #change only in b.
a
[1, 2] #no change in a
a.append(4)
a
[1, 2, 4] #change only in a.
b
[1, 2, 3] #no change in b.

Related

How to copy list elements in python

Regarding the code below:
A = [[1, 2], [3, 4]]
A[0] = A[1]
B = A[:][0]
B[0] = 5
print(A)
print(B)
I'm wondering why printing B gives [5, 4].
I thought that B = A[:][0] is the same as A[0][0], A[1][0], which would then be [3, 3]. Then, B[0] = 5, so it would print [5, 3].
Could someone kindly clear up my confusion?
after this line:
A[0] = A[1]
A = [[3,4],[3,4]]
A[:]
will return A itself, so when you access 0, you will get [3,4]
B = A[:][0]
and after putting 5, B = [5,4]
Here is your confusion:
I thought that B = A[:][0] is the same as A[0][0], A[1][0]
It's not. The : symbol in Python isn't used for that sort of shorthand like in some other languages. A[:] simply returns a copy of A, so A[:][0] is equivalent to A[0], which in this context is [3, 4].
(The more general use of the : is for an operation called slicing, which is when you copy only part of a list, from a given starting index to a given end index. If we omit the indices, they default to the start and end of the entire list, so we copy the whole thing.)
Using the copy() method is relatively straightforward.
a = [[1, 2], [3, 4]]
b = a.copy()
print(a)
print(b)
result
[[1, 2], [3, 4]]
[[1, 2], [3, 4]]
If you want to make copies of the nested lists, then you would extend to this:
a = [[1, 2], [3, 4]]
b = [a[0].copy(), a[1].copy()]

Duplicating a list from csv reader in Python

This is my csv file named 'test.csv':
aaa,,
ccc,ddd,
,eee,
And this is my code:
import csv
with open('test.csv', 'rt') as f:
a = list(csv.reader(f))
r = a[:]
for row in r:
for index in range(3):
if row[index] == '':
row[index] = 'xxx'
print(r)
print(a)
The result:
[['aaa','xxx','xxx'],['ccc','ddd','xxx'],['xxx','eee','xxx]]
[['aaa','xxx','xxx'],['ccc','ddd','xxx'],['xxx','eee','xxx]]
I only want to change the content in list 'r', but why the list 'a' didn't remain the original one [['aaa','',''],['ccc','ddd',''],['','eee','']]?
The reason this is happening is that the same reference to the internal lists is being maintained. Lets take this as integers instead:
If you have the list a = [1, 2, 3] you would expect b to also be [1, 2, 3] when you do b = a[:]. This is the case with your example. The list stores pointers to the other lists. If you just copy the list you will get those same pointers and be modifying the same underlying list as show below:
In [14]: a = [[1,2,3],[4,5,6],[7,8,9]]
In [15]: b = a[:]
In [16]: id(a)
Out[16]: 4499634512
In [17]: id(b)
Out[17]: 4499967296
In [18]: id(a[0])
Out[18]: 4499909664
In [19]: id(b[0])
Out[19]: 4499909664
Note that while id(a) and id(b) are different, id(a[0]) and id(b[0]) are the same. Doing b = a[:] creates a shallow copy of the list.
You need to make a deep copy. This can be done with the deepcopy function of the copy library.
In [20]: from copy import deepcopy
In [21]: c = deepcopy(a)
In [22]: id(c[0])
Out[22]: 4500008112
In [23]: id(a[0])
Out[23]: 4499909664
In [24]: a[0][0] = 42
In [25]: a
Out[25]: [[42, 2, 3], [4, 5, 6], [7, 8, 9]]
In [26]: c
Out[26]: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Hope this helps!
Well in that case you need to deep copy the list. r = a[:] does not do a deep copy.
Try:
import copy;
r = copy.deepcopy(a[:])
Because a is a list of lists, where the outer list is the rows and the inner lists are the columns within each row. Doing a[:] makes a copy of the outer list, but each element within that copied list is still a reference to the original inner list.
You should use copy.deepcopy instead.

How can I switch contents of lists?

I have two lists, a and b.
I want to reassign the content of a into b and the content of b into a.
or in other words, after running a few operations with two lists, I want to switch the names of the lists. so that the list that was named a would now be named b, and the list that was named b would now be named a.
this way is obviously wrong:
a=b
b=a
(because they are both b now)
so, is there a right way to do this?
python lets you do that as follows:
a, b = b, a
Example
a = [1,2,3]
b = [4,5,6]
a, b = b, a
>>> print a
[4,5,6]
>>> print b
[1,2,3]
This is because the expression on the right hand side is evaluated first and then assigned to variables on the left.
This might be not necessary for you, but good to remember that:
a = [1, 2, 3]
b = [3, 4, 5]
c = a # now 'c' points to the same list as 'a'
a[0] = 0 # let's change the first element
print a
# [0, 2, 3]
print c
# now let's use multiple assigment:
a, b = b, a
print a
# [3, 4, 5]
print c
# [0, 2, 3]
# list didn't change, but 'a' now point do different list
# but 'c' still point to the same, unchanged list, the one
# that is now referenced by 'b'
Above solution is fast and usually the best. But if you have the same list (or other mutable data structure) referenced by other names, you may swap contents of list instead:
a = [1, 2, 3]
b = [3, 4, 5]
c = a
a[0] = 0
a[:], b[:] = b[:], a[:] # now we actually swap contents
# 'a' and 'c' still point to the same list,
# but values are from 'b' list
print a
# [3, 4, 5]
print b
# [0, 2, 3]
print c
# [3, 4, 5]

Python - Declare two variable with the same values at the same time

a=[1,2,3]
b=[1,2,3]
Is there a way to do this on one line? (obviously not with ";")
a,b=[1,2,3]
doesn't work because of
a,b,c=[1,2,3]
a=1
b=2
c=3
In [18]: a,b=[1,2,3],[1,2,3]
In [19]: a
Out[19]: [1, 2, 3]
In [20]: b
Out[20]: [1, 2, 3]
you may also want to do this:
In [22]: a=b=[1,2,3]
In [23]: a
Out[23]: [1, 2, 3]
In [24]: b
Out[24]: [1, 2, 3]
but be careful that, a is b is True in this case, namely, a is just a reference of b
a,b,c = [[1,2,3] for _ in range(3)]
each points to a different object
Edit: as found by DSM in order for the following lines to work you need to declare b as a list in order for my code to work (so this is no longer on one line, but I will leave it here as a reference). Changed the order as suggested by Paulo Bu
a=[1,2,3]
b=a[:]
Old code:
b=[]
a=b[:]=[1,2,3]
This assigns the values to b and then copies all the values from b to a.
If you do it like this:
a=b=[1,2,3]
and then change
b[1] = 0
a would also be changed
>>> a = b = [1, 2, 3]
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> b = [3, 2, 1]
>>> b
[3, 2, 1]
>>> a
[1, 2, 3]
a,b = [1,2,3],[1,2,3] does it in one line and points to different objects.
a=b=[1,2,3] is clear but points to the same object.
Try a.pop(1) in both cases and you will see the difference.
Note... this solution
a=b=[1,2,3]
results in assigning the same (mutable) list to ivar's a and b. The original question shows 2 different lists being assigned to a & b.
This solution suffers the same problem:
>>> a,b = ([1,2,3],)*2
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> b.append(4)
>>> b
[1, 2, 3, 4]
>>> a
[1, 2, 3, 4]

python [:] notation and range

I've been tasked with converting some python code over to Java. I came across some notation I'm not familiar with and can't seem to find any information on. I'm guessing this is a lack of keywords on my part.
I've sanitized the code and hard coded some basic values for simplicity.
index_type = c_int * 1000 #size of int, basically 1000 integers?
indexes = index_type() # not entirely sure what this does
indexes[:] = range(2000, 3000)[:] # no idea
# c_int equals 4
The logic doesn't really matter to me, I'm just trying to figure out what's going on in terms of datatypes and converting to Java.
This is called "slicing". See the tutorial sections on Strings and Lists for a good description of how it works. (In your example, it's actually a ctypes array that's being sliced, not a list, but they work the same way. So, for simplicity, let's talk about lists.)
The notation indexes[:] is a slice of the entire list. So, you can do this:
a = [1, 2, 3]
b = a[:]
… to get a copy of the whole list, and this:
a[:] = [4, 5, 6]
… to replace the contents of the whole list.
You may wonder how this is different from just using a itself. The difference is that in the first case, b = a doesn't copy the list, it just makes another reference to the same list, and in the second case, a = [4, 5, 6] doesn't mutate the list, it rebinds a to refer to a new list. Here's an example that shows the difference:
>>> a = [1, 2, 3]
>>> b = a # b is now the same list as a
>>> a[0] = 10 # so changing that list is visible to b
>>> b
[10, 2, 3]
>>> a = [1, 2, 3]
>>> b = a[:] # b is now a new list, with a copy of a
>>> a[0] = 10 # so changing the original list doesn't affect b
>>> b
[1, 2, 3]
>>> a = [1, 2, 3]
>>> b = a # b is now the same list as a
>>> a = [4, 5] # but now a is a different list
>>> a[0] = 10
>>> b
[1, 2, 3]
>>> a = [1, 2, 3]
>>> b = a # b is now the same list as a
>>> a[:] = [4, 5] # and we've replaced the contents
>>> b
[4, 5]
You may wonder why anyone would use range(2000, 3000)[:] on the right side of the assignment.
Well, I wonder the same thing.
If this is Python 2.x, range(2000, 3000) is a brand-new list. You replace the contents of indexes with the contents of that range list, then give up your only reference to the range list. There is no way anyone could possibly end up sharing it, so there is no good reason to make an extra copy of it, unless you're worried that your computer has too much CPU and too much RAM and might be getting bored.
If this is Python 3.x, range(2000, 3000) is a smart range object. And range(2000, 3000)[:] is a new and equal range object. The copying this time is a lot cheaper, but it's exactly as unnecessary.
The other answers are talking about what x[:] means as an expression. But when this notation is used as the target of an assignment (i.e., on the left side of the =), it means something different. It is still a slice of the object, but it doesn't create a copy; rather, it assigns the given value (the right hand side) to the specified "part" of the object. If you use [:], the slice is the whole object, so its contents will be replaced by what you pass.
An example:
>>> x = [1, 2, 3]
>>> x[:] = [8, 8]
>>> x
[8, 8]
Notice that you can replace the contents by new contents of different length.
If you use a partial slice, only part of the contents will be replaced:
>>> x = [1, 2, 3, 4]
>>> x[1:3] = [8, 8, 88]
>>> x
[1, 8, 8, 88, 4]
The notation:
[a:b]
Is a range that starts at a and ends at b. When you leave one of them blank, it counts as "beginning" and "end", respectively.
Basically, your indexes is a list of values, each one has a number to associate with it that shows its order inside indexes. It starts at 0 and progresses until the end. The line of code:
indexes[:]
Simply means
"first value of indexes to last value of indexes"
I hope this helps,
happy coding!
It means getting a COPY of the original list, since lists are mutable in python, for example
>>a = [1,2,3]
>>b = a
>>a[0] = 3
>>a
[3, 2, 3]
>>b
[3, 2, 3]
>>b = a[:]
>>a[0] = 0
>>a
[0, 2, 3]
>>b
[3, 2, 3]
[:] is an example of slice notation, Python's way to specify any number of items in a sequence. [:] specifies the entire sequence. The notation is succinct and powerful in what it can express. Some other possible forms are:
sequence[n] #an index to one item (not a slice)
sequence[n:] #the nth item to the end of the sequence
sequence[:n] #all items until the nth item (exclusive)
sequence[m:n] #the mth item until the nth item (exclusive)
sequence[:] #all items
sequence[::2] #every other item
sequence[::-1] #all items in reverse order
When slicing is used to modify items in a sequence, the item(s) are modified:
>>> a = [1, 2, 3]
>>> a[0:1] = [4] #same as a[0] = 4
>>> a
[4, 2, 3]
>>> a[:] = [1, 2, 3, 4] #the sequence can be made larger
>>> a
[1, 2, 3, 4]
>>> a[:] = [] #or smaller
>>> a
[]
When slicing is used to read items in a sequence, a shallow copy of the item(s) are returned instead of a reference to the item. The term shallow copy means that if a copied item is itself a sequence, the item will refer to the original sequence, not a copy of it:
For instance:
>>> a = [1, 2, 3]
>>> b = a[0]
>>> b #b has the same value as, but does not reference the same object as a[0]
1 #this is a copy of a[0]
>>> a = [1, 2, 3]
>>> b = a[:]
>>> b #b has the same values as, but does not reference the same object as a
[1, 2, 3] #this is a copy of list a
>>> a = [1, 2, 3]
>>> b = [a, 2, 3]
>>> c = b[:] #c is a hallow copy of b
>>> c
[[1, 2, 3], 2, 3]
>>> b[:] = [] # modifying b does not affect c
>>> c
[[1, 2, 3], 2, 3]
>>> a[0] = 4 # modifying a will affect a in c
>>> c
[[4, 2, 3], 2, 3]

Categories