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[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’]

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

Next: Experience - Practical: Containers