I want to understand small snippet here in python:
>>> x = ['foo', [1,2,3], 10.4]
>>> y = list(x)
>>> y[0]
'foo'
>>> y[0] = "fooooooo"
>>> y[1]
[1, 2, 3]
>>> y[1][0]=4
>>> print x
['foo', [4, 2, 3], 10.4]
>>> print y
['fooooooo', [4, 2, 3], 10.4]
>>> z = ['foo', [1,2,3], 10.4]
>>> x = ['foo', [1,2,3], 10.4]
>>> y = list(x)
>>> y[0] = "fooooooo"
>>> y[1]
[1, 2, 3]
>>> y[1][0]=4
>>> print x
['foo', [4, 2, 3], 10.4]
>>> print y
['fooooooo', [4, 2, 3], 10.4]
>>> print z
['foo', [1, 2, 3], 10.4]
>>> y = list(z)
>>> y[1][0]=6
>>> print y
['foo', [6, 2, 3], 10.4]
>>> y = list(z)
>>> print z
['foo', [6, 2, 3], 10.4]
>>> print x
['foo', [4, 2, 3], 10.4]
How this works.
if change the list element of y its getting reflecting to x. It may be very basic of python but still i am not getting hold on this
y = list(x)
Above statement creates a shallow copy of the list x.
From the documentation:
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
In here, you do get a new object y but since the list inside it is a mutable object, you get the reference to the original object. And, if you keep creating these shallow copies the list object will be shared among all the copies.
You can check it using id:
>>> id(x)
140183460314288
>>> id(y)
140183460372992 # this is different from y
>>> id(x[1])
140183460314864
>>> id(y[1]) # this is same as x[1]
140183460314864
>>> y1 = list(y) # another shallow copy from y
>>> id(y1[1])
140183460314864 # this is still same
If you expect a behavior where you do need to modify the contents of y without affecting x, you need to perform deepcopy:
>>> >>> from copy import deepcopy
>>> z = deepcopy(x)
>>> id(z[1])
140183460405400 # this is different now because of deepcopy
You can see that this id is different than id(x[1]), and now if you try to modify the contents they won't be reflected in x.
I'm guessing the only thing you're confused by is the fact that setting the elements of the nested list of one variable will change the nested lists of all other variables. The reason is simple: Python is using the same nested list (as in, the exact same memory) in each variable. When you say y = list(x), Python copies all of the atomic elements of x into y but merely copies a reference to the nested list. Since the same nested list is used everywhere, modifying it one place modifies it everywhere.
You can also see similar behavior by playing around with l1 = [0]*3 and l2 = [[0]]*3; the differences between how l1 and l2 behave are another example of the behavior you're observing.
>>> x = ['foo', [1,2,3], 10.4]
>>> y = list(x)
y is a copy of x. y[1] contains a copy of the reference in x to the list [1,2,3]. So y[1] and x[1] are 2 references to the same list.
Related
I am a little confused on how shallow copy works, my understanding is when we do new_obj = copy.copy(mutable_obj) a new object is created with elements of it still pointing to the old object.
Example of where I am confused -
## assignment
i = [1, 2, 3]
j = i
id(i[0]) == id (j[0]) # True
i[0] = 10
i # [10, 2, 3]
j # [10, 2, 3]
## shallow copy
k = copy.copy(i)
k # [10, 2, 3]
id(i) == id(k) # False (as these are two separate objects)
id(i[0]) == id (k[0]) # True (as the reference the same location, right?)
i[0] = 100
id(i[0]) == id (k[0]) # False (why did that value in that loc change?)
id(i[:]) == id (k[:]) # True (why is this still true if an element just changed?)
i # [100, 2, 3]
k # [10, 2, 3]
In shallow copy, isn't k[0] just pointing to i[0] similar to assignment? Shouldn't k[0] change when i[0] changes?
Why I expect these to be same, because -
i = [1, 2, [3]]
k = copy(i)
i # [1, 2, [3]]
k # [1, 2, [3]]
i[2].append(4)
i # [1, 2, [3, 4]]
k # [1, 2, [3, 4]]
id(i[0]) == id (k[0]) # True
id(i[2]) == id (k[2]) # True
id(i[:]) == id (k[:]) # True
id(i) == id(k) # False (as these are two separate objects)
Correct.
id(i[0]) == id (k[0]) # True (as the reference the same location, right?)
Correct.
i[0] = 100
id(i[0]) == id (k[0]) # False (why did that value in that loc change?)
It changed because you changed it in the previous line. i[0] was pointing 10, but you changed it to point to 100. Therefore, i[0] and k[0] now no longer point to the same spot.
Pointers (references) are one way. 10 does not know what is pointing to it. Neither does 100. They are just locations in memory. So if you change where i's first element is pointing to, k doesn't care (since k and i are not the same reference). k's first element is still pointing to what it always was pointing to.
id(i[:]) == id (k[:]) # True (why is this still true if an element just changed?)
This one's a bit more subtle, but note that:
>>> id([1,2,3,4,5]) == id([1,2,3])
True
whereas
>>> x = [1,2,3,4,5]
>>> y = [1,2,3]
>>> id(x) == id(y)
False
It has to do with some subtleties of garbage collection and id, and it's answered in depth here: Unnamed Python objects have the same id.
Long story short, when you say id([1,2,3,4,5]) == id([1,2,3]), the first thing that happens is we create [1,2,3,4,5]. Then we grab where it is in memory with the call to id. However, [1,2,3,4,5] is anonymous, and so the garbage collector immediately reclaims it. Then, we create another anonymous object, [1,2,3], and CPython happens to decide that it should go in the spot that it just cleaned up. [1,2,3] is also immediately deleted and cleaned up. If you store the references, though, GC can't get in the way, and then the references are different.
Mutables example
The same thing happens with mutable objects if you reassign them. Here's an example:
>>> import copy
>>> a = [ [1,2,3], [4,5,6], [7,8,9] ]
>>> b = copy.copy(a)
>>> a[0].append(123)
>>> b[0]
[1, 2, 3, 123]
>>> a
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]
>>> b
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]
>>> a[0] = [123]
>>> b[0]
[1, 2, 3, 123]
>>> a
[[123], [4, 5, 6], [7, 8, 9]]
>>> b
[[1, 2, 3, 123], [4, 5, 6], [7, 8, 9]]
The difference is when you say a[0].append(123), we're modifying whatever a[0] is pointing to. It happens to be the case that b[0] is pointing to the same object (a[0] and b[0] are references to the same object).
But if you point a[0] to a new object (through assignment, as in a[0] = [123]), then b[0] and a[0] no longer point to the same place.
In Python all things are objects. This includes integers. All lists only hold references to objects. Replacing an element of the list doesn't mean that the element itself changes.
Consider a different example:
class MyInt:
def __init__(self, v):
self.v = v
def __repr__(self):
return str(self.v)
>>> i = [MyInt(1), MyInt(2), MyInt(3)]
[1, 2, 3]
>>> j = i[:] # This achieves the same as copy.copy(i)
[1, 2, 3]
>>> j[0].v = 7
>>> j
[7, 2, 3]
>>> i
[7, 2, 3]
>>> i[0] = MyInt(1)
>>> i
[1, 2, 3]
>>> j
[7, 2, 3]
I am creating a class MyInt here which just holds an int.
By modifying an instance of the class, both lists "change". However as I replace a list entry, the lists are now different.
The same happens with integers. You just can't modify them.
In the first case j = i is an assignment, both j and i point to the same list object. When you change an element of the list object and print i and j, since both i and j point to same list object, and it is the element and not the list object which has changed, so both will print the same output.
In the second case k = copy.copy(i) is a shallow copy, in which a copy of list object and copy of nested references is made but the internal immutable objects are not copied.
A shallow copy doesn't create a copy of nested objects, instead it just copies the reference of nested objects. Please refer this https://www.programiz.com/python-programming/shallow-deep-copy
Thus i and k have different set of references pointing to the same immutable objects. When you do i[0] = 100, the reference in list i points to a new int object with value 100, but the reference in k still references the old int object with value 10.
Is it possible to pass a slice of a list into a function and modify the list via the slice?
This doesn't seem to work:
def foo(a_list):
a_list[0]='abc'
x=[1,2,3,4]
foo(x[0:2])
I want x to now be x=['abc',2,3,4]
No. A "list slice" in the sense you describe is nothing but a new list. When you do x[0:2], the resulting list does not "know" that it was created as a slice of another list. It's just a new list.
What you could do is pass in the slice object itself, separately from the list:
def foo(a_list, a_slice):
a_list[a_slice]='abc'
>>> foo(x, slice(0, 2))
>>> x
['a', 'b', 'c', 3, 4]
No! Slices create copies of lists (see the documentation). You can do this:
>>> x = [1, 2, 3, 4]
>>> x[1:3] = [7, 8, 9]
>>> X
[1, 7, 8, 9, 4]
But, when you get a new list from a slicing operation, it's a copy, and thus changes to it won't affect the original:
>>> x = [1, 2, 3, 4]
>>> y = x[1:3]
>>> y[0] = 5
>>> y
[5, 3]
>>> x
[1, 2, 3, 4]
def foo(a_list):
a_list[0]='abc'
return a_list
x=[1,2,3,4]
foo(x) #it returns x=['abc',2,3,4]
Assumably you're trying to pass in x[0:2], rather than x[0,2], but the reason it doesn't work is because when you create a slice you are creating a subarray copy of x.
You are not operating on the same instance of that array, what you are doing is passing an entirely new array. Passing in 'x' alone would work, but passing in x[0:2] would not unless you specifically wrote it as x[0:2] = foo(x[0:2]) and had foo() return a_list.
As brenns10 explained, slices create a copy (even in python 3.0) of your origional data.
You could do something like the following:
def foo(x):
x[0] = 'abc'
return x
x = [0, 1, 2, 3]
x[0:2] = foo(x[0:2]) # x = ['abc', 1, 2, 3]
While this gives you the desired outcome, it doesn't exactly work as you would want. This could be problematic if needing to perform large slices as you'd have to perform a lot of copying.
The del statement in python, when applied to a list, does something very strange. It not only deletes the entry in the list in question, but also "backtracks" to delete the same element in whatever list the variable was derived from.
For instance:
>>> x
[1, 2, 3, 4, 5]
>>> z=x
>>> del z[1]
>>> z
[1, 3, 4, 5]
>>> x
[1, 3, 4, 5]
In other words, even though I apply delete to z, the deletion is also applied to x. This "backtracking" doesn't come up with any other commands or functions, e.g.
>>> x=[1,2,3,4,5]
>>> z=x
>>> z+[6]
[1, 2, 3, 4, 5, 6]
>>> x
[1, 2, 3, 4, 5]
applying the add function to z leaves x unaffected, as it very well should.
This is causing me all sorts of headaches with the delete 'NA' scrip that I'm working on. Do you know what's going on with the del statement, and how can I get around this problem so that if I set z=x and then apply del to z, x remains unchanged?
z = x does not make a copy of the list. z and x now point to the same list. Any modification of that list will be visible through both names, z and x.
With z + [6], you did not assign the result back to z. Try printing z after doing that. It will be unchanged, just like x.
z += [6] would be necessary to assign the result back to z and it would have the same behavior as your first example.
You can copy the list using one of these methods:
z = x[:]
z = list(x)
I have a list L of objects (for what it's worth this is in scons). I would like to create two lists L1 and L2 where L1 is L with an item I1 appended, and L2 is L with an item I2 appended.
I would use append but that modifies the original list.
How can I do this in Python? (sorry for the beginner question, I don't use the language much, just for scons)
L1 = L + [i1]
L2 = L + [i2]
That is probably the simplest way. Another option is to copy the list and then append:
L1 = L[:] #make a copy of L
L1.append(i1)
L1=list(L)
duplicates the list. I guess you can figure out the rest :)
You can make a copy of your list
>>> x = [1, 2, 3]
>>> y = list(x)
>>> y.append(4)
>>> y
[1, 2, 3, 4]
>>> z = list(x)
>>> z.append(5)
>>> z
[1, 2, 3, 5]
or use concatenation, which will make a new list
>>> x = [1, 2, 3]
>>> y = x + [4]
>>> z = x + [5]
>>> y
[1, 2, 3, 4]
>>> z
[1, 2, 3, 5]
The former is probably a touch more idiomatic/common, but the latter works fine in this case. Some people also copy using slicing (x[:] makes a new list with all the elements of the original list x referred to) or the copy module. Neither of these are awful, but I find the former a touch cryptic and the latter a bit silly.
Can someone explain why the example with integers results in different values for x and y and the example with the list results in x and y being the same object?
x = 42
y = x
x = x + 1
print x # 43
print y # 42
x = [ 1, 2, 3 ]
y = x
x[0] = 4
print x # [4, 2, 3]
print y # [4, 2, 3]
x is y # True
The best explanation I ever read is here:
http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html#other-languages-have-variables
Because integers are immutable, while list are mutable. You can see from the syntax. In x = x + 1 you are actually assigning a new value to x (it is alone on the LHS). In x[0] = 4, you're calling the index operator on the list and giving it a parameter - it's actually equivalent to x.__setitem__(0, 4), which is obviously changing the original object, not creating a new one.
If you do y = x, y and x are the reference to the same object. But integers are immutable and when you do x + 1, the new integer is created:
>>> x = 1
>>> id(x)
135720760
>>> x += 1
>>> id(x)
135720748
>>> x -= 1
>>> id(x)
135720760
When you have a mutable object (e.g. list, classes defined by yourself), x is changed whenever y is changed, because they point to a single object.
That's because when you have a list or a tuple in python you create a reference to an object.
When you say that y = x you reference to the same object with y as x does.
So when you edit the object of x y changes with it.
As the previous answers said the code you wrote assigns the same object to different names such aliases.
If you want to assign a copy of the original list to the new variable (object actually)
use this solution:
>>> x=[1,2,3]
>>> y=x[:] #this makes a new list
>>> x
[1, 2, 3]
>>> y
[1, 2, 3]
>>> x[0]=4
>>> x
[4, 2, 3]
>>> y
[1, 2, 3]