## Memory and containers

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 = "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 = "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 ='d'
z ='e'


We can see that x is a list containing a list  and a string 

x


[[‘a’, ‘d’], ‘c’]

y


[[‘a’, ‘d’], ‘c’]

You can see that as z contains x, when x  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’]

### Identity vs Equality

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