XClose

COMP0233: Research Software Engineering 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. It is like a list, execpt it cannot be changed. It is defined with round brackets.

In [30]:
my_tuple = ("Hello", "World")
In [31]:
my_tuple
Out[31]:
('Hello', 'World')

Trying to modify one of its values will fail:

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.

Supplementary material: The copies that we make through slicing are called shallow copies: we don't copy all the objects they contain, only the references to them. This is why the nested list in x[0] is not copied, so z[0] still refers to it. It is possible to actually create copies of all the contents, however deeply nested they are - this is called a deep copy. Python provides methods for that in its standard library, in the copy module. You can read more about that, as well as about shallow and deep copies, in the library reference.

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 'str' literal. Did you mean "=="?
<>:3: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
/tmp/ipykernel_11182/1808182476.py:3: SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
  print("Hello" is word)