1
x = [1,3,6,[18]]
y = list(x)
print(y)
x[3][0] = 15

x[1] = 12
print(x)
print(y)

In the above code, updating x[3][0] in x is reflected in y (which is the list of x), but the update in x[1] = 12 is not reflected in y

Why this is not getting updated in y?

Daniel Hao
  • 4,922
  • 3
  • 10
  • 23
Maniks
  • 11
  • 1
  • 1
    Because list `y` is *shallow* copy of list `x` which means that `list(x)` doesn't recursively copy each element of `x` so element on index `3` contains reference to same list both in `x` and `y`. To create full *(deep)* copy use [`copy.deepcopy()`](https://docs.python.org/3/library/copy.html#copy.deepcopy). – Olvin Roght Jul 21 '22 at 15:40
  • You might want to check something like `x is y` or just look at the output of `id(x)` and `id(y)` – Charmander35 Jul 21 '22 at 15:41
  • Because `x[3]` and `y[3]` refer to the same list, when you update that list via `x[3][0]` the change is visible from `y[3]` too. But when you change `x[1]` you are only changing an item in `x` ... `x` and `y` are different lists – Anentropic Jul 21 '22 at 15:42

4 Answers4

4

A picture worth thousand words. Here is the picture that is missing: (let's pretend forgetting all *terminology for a moment now)

Credits to all posts. Labor (of the wording & picturing) is mine.

First thing first, when you try to use list constructor to create y: list(x) or [:] it just produces a shallow copy (ie. the outermost container is duplicated, but the copy is filled with reference to the same item (as [18] shown in picture)

So later if you update x's [18] it will update the reference point - that's why both lists see the change of 15. But when you update other object, it directly change the integer object in the container x but not in y.

Lesson learned - Copies are shallow by Default! in Python. list of list

# See different code example:
x  = [1,3,6,[18]]

y =  x[:]     # new list, but it's shallow copy (last item)

print(x, id(x))   # different memory
print(y, id(y))   # but same content to start with 

x[3][0] = 15      # assign new value - reflect in both x, y
x[1] = 12         # assign new value - only affect x

print(x, id(x)) 

print(y, id(y))   # last item got changed too; because sharing

https://i.stack.imgur.com/Qy0U8.png

Daniel Hao
  • 4,922
  • 3
  • 10
  • 23
0

What is happening is a shallow copy. When you wrote y=list(x), this created a reference from y to x, and not an actual copy. What you need is a deep copy, a copy that creates a new list(including nested lists) instead of referencing the old one. .copy() won't work, because nested lists are still references even though the rest of the copied list is a separate list.

Here is the code for a deep copy:

import copy
x=[1,3,6,[18]]
y=copy.deepcopy(x)
print(y)
x[3][0]=15
x[1]=12
print(x)
print(y)
piip
  • 1
  • 3
  • 2
    *"this created a reference from y to x, and not an actual copy"* - no, it actually creates a shallow copy, just in different way. – Olvin Roght Jul 21 '22 at 15:52
  • Hi, thanks. Pls clarify this. x[3][0]=15 - this assignment is reflected y but x[1]=12 - this assignment is not reflected in y - can you pls explain? – Maniks Jul 21 '22 at 16:03
  • 1
    And try to read/reflect and understand the ealier comments from @OlvinRoght. And you can add ```print(id(x))``` and ```print(id(y))``` too to see if the *memory address* of these 2 diff objects. – Daniel Hao Jul 21 '22 at 18:21
0

By using list() you create a new reference (y) to the same addresses in memory that (x) is referring.

Element 1 in x and y is an integer which is immutable. Once you use x[1]=12 basically you create a new object in memory for number 12 and x[1] will refer to that object. However, y[1] still refers to previous object (i.e. 3).

Element 3 in in x and y is a list which is mutable. Once, you use x[3][0], it updates the same object in memory. So, both x and y reflect the change.

I hope I could convey the concept.

Ali
  • 111
  • 4
  • A correction, if I may. The first statement is incorrect. Using `list()` will create a *shallow copy* of the original list, with a *new* memory addresses. However, as this is a shallow copy, (in this case) `x[3]` and `y[3]` will have the *same* memory address. This is where OP’s confusion is originating; the fact that a shallow copy has been made. – S3DEV Jul 21 '22 at 20:10
  • @S3DEV, you're right, but the first statement is correct too :) You just rephrased it. You create a new reference to the same memory addresses by using shallow copy. Quote from Python.org: "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." https://docs.python.org/3/library/copy.html – Ali Jul 21 '22 at 21:59
  • “You create a new reference to the same memory addresses by using shallow copy.” Not quite. But rather, you create a *new* object (in a new memory location), with any nested (mutable) objects inside the parent object simply being references. – S3DEV Jul 21 '22 at 23:35
  • Perhaps [have a read of this post](https://stackoverflow.com/q/72958108/6340496) to help add some context between shallow and deep copying. – S3DEV Jul 21 '22 at 23:45
  • Thanks for for sharing the link. I understand the concept shallow copy and deep copy. However, I don't see the point you're trying to highlight here; is it getting the terminology right or else? Since I already confirmed your comment regarding list() creating a shallow copy. However, to be accurate, array y (yes, you're right. it has a location on memory) will have references to memory addresses of all elements of array x (both mutable and immutable elements). I don't explain the rest as it's already in the main post. – Ali Jul 22 '22 at 02:49
0

They do not behave differently. Changes to x are never reflected in y.

x = [1,3,6,[18]]
y = list(x)
print(y) # [1,3,6,[18]]
x[3] = [15]
print(y) # [1,3,6,[18]]

However, changes to the 1-element list are reflected both in x and in y:

z = [18]
x = [1,3,6,z]
y = list(x)
print(y) # [1,3,6,[18]]
z[0] = 15
print(y) # [1,3,6,[15]]

The reason why integers and lists are different, the reason why you don't see changes to integers reflected in y is because it's possible to make changes to a list, but it's not possible to make changes to int:

x = [1,3,6,[18]]
y = list(x)
print(y) # [1,3,6,[18]]
3 = 12 # SyntaxError: cannot assign to literal here.

In python, every type is either mutable or immutable. Lists are mutable; integers are immutable.

Stef
  • 13,242
  • 2
  • 17
  • 28