The way memory works with containers can be important:
x = list(range(3))
print(x)
[0, 1, 2]
y = x
print(y)
[0, 1, 2]
z = x[0:3]
y[1] = "Gotcha!"
print(x)
print(y)
print(z)
[0, ‘Gotcha!’, 2]
[0, ‘Gotcha!’, 2]
[0, 1, 2]
This may seem a little confusing initially.
Using ‘=’ just assigns a label to the list.
z[2] = "Really?"
print(x)
print(y)
print(z)
[0, ‘Gotcha!’, 2]
[0, ‘Gotcha!’, 2]
[0, 1, ‘Really?’]
While ‘y’ is a second label on the same object, z is a separate object with the same data. Writing ‘x[:]’’ creates a new list containing all the elements of ‘x’ (remember: ‘[:]’’ is equivalent to ‘[0:
This is the case whenever we take a slice from a list, not just when taking all the elements with ‘[:]’’.
The difference between ‘y=x’ and ‘z=x[:]’’ is important!
Nested objects make it even more complicated:
x = [['a', 'b'], 'c']
y = x
z = x[0:2]
x[0][1] ='d'
z[1] ='e'
We can see that x is a list containing a list [0][0] and a string [0][1]
x
[[‘a’, ‘d’], ‘c’]
y
[[‘a’, ‘d’], ‘c’]
You can see that as z contains x, when x [0][1] was updated, the corresponding element in z was also updated.
While ‘y’ is a second label on the same object, ‘z’ is a separate object with the same data.
z
[[‘a’, ‘d’], ‘e’]
Having the same data is different from being the same actual object in memory:
print([1, 2] == [1, 2])
print([1, 2] is [1, 2])
True
False
The == operator checks, element by element, that two containers have the same data.
The is
operator checks that they are actually the same object.
[0,1,2] == range(3)
False
[0, 1, 2] is range(3)
False
But, and this point is really subtle, for immutables, the python language might save memory by reusing a single instantiated copy.
print("Hello" == "Hello")
print("Hello" is "Hello")
True
True
This can be useful in understanding problems like the one above:
x = range(3)
y = x
z = x[:]
x == y
True
x is y
True
x == z
True
x is z
False