XClose

An introduction to research programming with Python

Home
Menu

Containers

Containers are a data type that contains other objects.

Lists

Python's basic container type is the list

We can define our own list with square brackets:

In [1]:
[1, 3, 7]
Out[1]:
[1, 3, 7]
In [2]:
type([1, 3, 7])
Out[2]:
list

Lists do not have to contain just one type:

In [3]:
various_things = [1, 2, "banana", 3.4, [1, 2]]

We access an element of a list with an int in square brackets:

In [4]:
one = 1
two = 2
three = 3
In [5]:
my_new_list = [one, two, three]
In [6]:
middle_value_in_list = my_new_list[1]
In [7]:
middle_value_in_list
Out[7]:
2
In [8]:
[1, 2, 3][1]
Out[8]:
2
In [9]:
various_things[2]
Out[9]:
'banana'
In [10]:
index = 2
various_things[index]
Out[10]:
'banana'

Note that list indices start from zero.

We can quickly make a list containing a range of consecutive integer numbers using the built-in range function

In [11]:
count_to_five = list(range(5))
print(count_to_five)
[0, 1, 2, 3, 4]

We can use a string to join together a list of strings:

In [12]:
name = ["Grace", "Brewster", "Murray", "Hopper"]
print(" ".join(name))
Grace Brewster Murray Hopper

And we can split up a string into a list:

In [13]:
"Ernst Stavro Blofeld".split(" ")
Out[13]:
['Ernst', 'Stavro', 'Blofeld']

We can an item to a list:

In [14]:
name.append("BA")
print(" ".join(name))
Grace Brewster Murray Hopper BA

Or we can add more than one:

In [15]:
name.extend(["MS", "PhD"])
print(" ".join(name))
Grace Brewster Murray Hopper BA MS PhD

Or insert values at different points in the list

In [16]:
name.insert(0, "Admiral")
print(" ".join(name))
Admiral Grace Brewster Murray Hopper BA MS PhD

Sequences

Many other things can be treated like lists. Python calls things that can be treated like lists sequences.

A string is one such sequence type

In [17]:
print(count_to_five[1])
print("James"[2])
1
m
In [18]:
print(count_to_five[1:3])
print("Hello World"[4:8])
[1, 2]
o Wo
In [19]:
print(len(various_things))
print(len("Python"))
5
6
In [20]:
len([[1, 2], 4])
Out[20]:
2

Unpacking

Multiple values can be unpacked when assigning from sequences, like dealing out decks of cards.

In [21]:
mylist = ["Goodbye", "Cruel"]
a, b = mylist
print(a)
Goodbye
In [22]:
a = mylist[0]
b = mylist[1]

Checking for containment

The list we saw is a container type: its purpose is to hold other objects. We can ask python whether or not a container contains a particular item:

In [23]:
"Dog" in ["Cat", "Dog", "Horse"]
Out[23]:
True
In [24]:
"Bird" in ["Cat", "Dog", "Horse"]
Out[24]:
False
In [25]:
2 in range(5)
Out[25]:
True
In [26]:
99 in range(5)
Out[26]:
False
In [27]:
"a" in "cat"
Out[27]:
True

Mutability

An list can be modified:

In [28]:
name = "Grace Brewster Murray Hopper".split(" ")
print(name)
['Grace', 'Brewster', 'Murray', 'Hopper']
In [29]:
name[0:3] = ["Admiral"]
name.append("PhD")

print(" ".join(name))
Admiral Hopper PhD

Tuples

A tuple is an immutable sequence:

In [30]:
my_tuple = ("Hello", "World")
In [31]:
my_tuple
Out[31]:
('Hello', 'World')
In [32]:
my_tuple[0] = "Goodbye"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [32], line 1
----> 1 my_tuple[0] = "Goodbye"

TypeError: 'tuple' object does not support item assignment

str is immutable too:

In [33]:
fish = "Hake"
fish[0] = "R"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In [33], line 2
      1 fish = "Hake"
----> 2 fish[0] = "R"

TypeError: 'str' object does not support item assignment

But note that container reassignment is moving a label, not changing an element:

In [34]:
fish = "Rake"  ## OK!

Supplementary material: Try the online memory visualiser for this one.

Memory and containers

The way memory works with containers can be important:

In [35]:
x = list(range(3))
print(x)
[0, 1, 2]
In [36]:
y = x
print(y)
[0, 1, 2]
In [37]:
z = x[0:3]
y[1] = "Gotcha!"
print(x)
print(y)
print(z)
[0, 'Gotcha!', 2]
[0, 'Gotcha!', 2]
[0, 1, 2]
In [38]:
z[2] = "Really?"
print(x)
print(y)
print(z)
[0, 'Gotcha!', 2]
[0, 'Gotcha!', 2]
[0, 1, 'Really?']

Supplementary material: This one works well at the memory visualiser.

In [39]:
x = ["What's", "Going", "On?"]
y = x
z = x[0:3]

y[1] = "Gotcha!"
z[2] = "Really?"
In [40]:
x
Out[40]:
["What's", 'Gotcha!', 'On?']

The explanation: While y is a second label on the same object, z is a separate object with the same data.

Nested objects make it even more complicated:

In [41]:
x = [["a", "b"], "c"]
y = x
z = x[0:2]

x[0][1] = "d"
z[1] = "e"
In [42]:
x
Out[42]:
[['a', 'd'], 'c']
In [43]:
y
Out[43]:
[['a', 'd'], 'c']
In [44]:
z
Out[44]:
[['a', 'd'], 'e']

Try the visualiser again.

Identity versus equality

Having the same data is different from being the same actual object in memory:

In [45]:
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.

In [46]:
my3numbers = list(range(3))
print(my3numbers)
[0, 1, 2]
In [47]:
[0, 1, 2] == my3numbers
Out[47]:
True
In [48]:
[0, 1, 2] is my3numbers
Out[48]:
False

But, and this point is really subtle, for immutables, the Python language might save memory by reusing a single instantiated copy. This will always be safe.

In [49]:
word = "Hello"
print("Hello" == word)
print("Hello" is word)
True
True
<>:3: SyntaxWarning: "is" with a literal. Did you mean "=="?
<>:3: SyntaxWarning: "is" with a literal. Did you mean "=="?
/tmp/ipykernel_6296/1808182476.py:3: SyntaxWarning: "is" with a literal. Did you mean "=="?
  print("Hello" is word)