Python Molecules: Lists, Tuples, and Dictionaries

So far, we have talked about some atomic data types: booleans, numbers, and strings. We rarely operate on only one object at a time though. We should be able to store them in some kind of container. This is where our additional data types come in: lists, tuples, and dictionaries.

1 Lists

A list is a sequence of objects which can be numbers, booleans, strings … even other lists. Lists are mutable, which means that we can add elements or change them in place.

1.1 Creating lists

Creating an empty list is easy, you just need squared brackets.

my_list = []
my_list
[]

By the way, empty lists are evaluated as False when converted to booleans.

bool([])
False

Again, this might be weird but will come in handy later on. An empty list is of course very boring. So maybe let’s create lists with content.

my_list = [1, 2, 3, 4, 5]
print(my_list)
[1, 2, 3, 4, 5]
my_list = ['A', 'list', 'with', 'strings.']
print(my_list)
['A', 'list', 'with', 'strings.']

Low and behold, a list can contain items of different types.

my_nonhomogenous_list = [1, 'One', True]
print(my_nonhomogenous_list)
[1, 'One', True]

A second way to create lists is using the list function which converts data types.

my_list = list()
print(my_list)
[]

When you convert a string to a list, it does not become a list with one element, namely the string. Instead, it breaks down the string into characters and stores each as an element.

list_of_characters = list('Wittgenstein')
print(list_of_characters)
['W', 'i', 't', 't', 'g', 'e', 'n', 's', 't', 'e', 'i', 'n']

This example also shows you that elements in a list do not have to be unique.

To add one more level of abstraction, note that lists can also contain other lists.

list_1 = ['a', 'b']
list_2 = ['c', 'd']

my_meta_list = [list_1, list_2]
print(my_meta_list)
[['a', 'b'], ['c', 'd']]

Our meta list contains two elements, list_1 and list_2, which each contain two elements. Note that this is not the same as having only one list with the four string elements.

1.2 Indexing and slicing lists

Indexing and slicing works similar to indexing and slicing for strings which we have encountered before.

my_list = ['a', 'b', 'c', 'd', 'e', 'f']
# first element
my_list[0]
'a'
# first to third element
my_list[0:3]
['a', 'b', 'c']
# every second element
my_list[::2]
['a', 'c', 'e']
# reverse order
my_list[::-1]
['f', 'e', 'd', 'c', 'b', 'a']

Now, with a list of lists this gets a little bit more complicated, we need the squared brackets two times.

list_1 = ['a', 'b']
list_2 = ['c', 'd']

my_meta_list = [list_1, list_2]
my_meta_list[0]
['a', 'b']

With this command, we fetch the first element from my_meta_list which is also a list consisting of two elements.

my_meta_list[1]
['c', 'd']
my_meta_list[0][1]
'b'

This command let’s us fetch the second element in list_1 which is the first element of my_meta_list. Do not get confused by the offsets here!

1.3 Add items and combine lists with append, insert, and extend

There are several ways to add elements to our lists. If we want to add an item, we can use the append method. Note that you do not have to reassign the list, since lists are mutable, i.e. they can be changed in place.

my_list = []
my_list.append(1)
my_list.append('Random string')
my_list.append(True)
my_list.append(['Hello', 'World'])
print(my_list)
[1, 'Random string', True, ['Hello', 'World']]

If you want to add an item at a specific place in the list, you can use the insert method.

my_philosophers = ['Wittgenstein', 'Sen', 'Moore']
my_philosophers.insert(2, 'Russell')
print(my_philosophers)
['Wittgenstein', 'Sen', 'Russell', 'Moore']

If you want to combine two lists, use the extend method or the += operator.

my_philosophers = ['Nozick', 'Sen', 'Rawls']
my_economists = ['Hayek', 'Keynes', 'Friedman']
my_philosophers.extend(my_economists)
print(my_philosophers)
['Nozick', 'Sen', 'Rawls', 'Hayek', 'Keynes', 'Friedman']
my_philosophers = ['Nozick', 'Sen', 'Rawls']
my_economists = ['Hayek', 'Keynes', 'Friedman']
my_economists += my_philosophers
print(my_economists)
['Hayek', 'Keynes', 'Friedman', 'Nozick', 'Sen', 'Rawls']

1.4 Delete items with del and remove

Again, we have different ways of deleting items of a list. I will only show two. The first deletes the item by offset using the del statement.

my_economists = ['Acemoglu', 'Johnson', 'Robinson']
del my_economists[1]
my_economists
['Acemoglu', 'Robinson']

Secondly, we can delete an item by value using remove.

my_economists = ['Acemoglu', 'Johnson', 'Robinson']
my_economists.remove('Johnson')
my_economists
['Acemoglu', 'Robinson']

1.5 Change items

Lists are mutable, this means that we can change individual items in place without creating a new list.

my_economists = ['Acemoglu', 'Johnson', 'Robinson']
my_economists[1] = 'Torvik'
print(my_economists)
['Acemoglu', 'Torvik', 'Robinson']

1.6 Some more list methods: index, count, and sort

Apart from these basic methods to add, delete and chane items, there exist other useful method which we should briefly look at. The first, index, gives us the offset of a specific item.

my_economists = ['Hayek', 'Keynes', 'Friedman']
my_economists.index('Keynes')
1

The second one, count, counts the number of occurences of an item.

my_economists = ['Acemoglu', 'Acemoglu', 'Johnson', 'Robinson']
my_economists.count('Acemoglu')
2

Finally, the sort method can be used to sort a list in place.

my_economists = ['Robinson', 'Johnson', 'Acemoglu']
print(my_economists)
['Robinson', 'Johnson', 'Acemoglu']
my_economists.sort()
print(my_economists)
['Acemoglu', 'Johnson', 'Robinson']

1.7 Testing for membership with in

A very useful feature is that we can test whether a certain object is an item of a list.

my_philosophers = ['Kant', 'Mill', 'Wittgenstein']
'Kant' in my_philosophers
True
'Monty Python' in my_philosophers
False

1.8 Caveat: New lists

There is one behavior of lists which might puzzle you.

my_economists = ['Hayek', 'Keynes']
my_philosophers = my_economists
my_economists.append('Marx')
print(my_economists)
['Hayek', 'Keynes', 'Marx']

So far, this is probably what you expected. However, how does the list my_philosophers look like?

print(my_philosophers)
['Hayek', 'Keynes', 'Marx']

It also changed. Instead of creating a copy of the original list and assigning it to the variable my_philosophers, Python just attaches a second label to the original list objec. So, no matter whether you use the name my_economists or my_philosopers, both refer to the same object. To avoid this behavior you have to make explicit copies of the original list. This can be done using either list command, the copy method or the complete copy via slicing [:].

my_economists = ['Hayek', 'Keynes']

# ways of copying lists 
my_philosophers1 = list(my_economists)
my_philosophers2 = my_economists.copy()
my_philosophers3 = my_economists[:]

my_economists.append('Marx')

print(my_philosophers1)
print(my_philosophers2)
print(my_philosophers3)
['Hayek', 'Keynes']
['Hayek', 'Keynes']
['Hayek', 'Keynes']

2 Tuples

2.1 Creating a tuple

There is another ‘container’ object which is similar to lists, namely tuples. The main difference is that tuples are immutable, so once they are created you cannot change them. They are created by using round brackets. Note that tuples, just as lists, can also be used to store arbitrary objects.

my_tuple = ('1', 1, True)
my_tuple
('1', 1, True)

Actually that is not completely true that you create tuples with round brackets. You can also create tuples without them. If you have only item you just need a trailing comma, if you have more elements, you just have to put commas between each of the items.

not_a_tuple = ('Wittgenstein')
type(not_a_tuple)
str
one_item_tuple = 'Wittgenstein',
type(one_item_tuple)
tuple
another_tuple = 'Acemoglu', 'Johnson', 'Robinson'
type(another_tuple)
tuple

To convert other objects into tuples, you can use the tuple function.

my_list = ['This', 'is', 'a', 'list', 'of', 'strings']
my_list
['This', 'is', 'a', 'list', 'of', 'strings']
my_tuple = tuple(my_list)
my_tuple
('This', 'is', 'a', 'list', 'of', 'strings')

So, we have tuple, but the strings are not a good description of a list. Maybe change it?

my_tuple[3] = 'tuple'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/tmp/ipykernel_2290/2947766814.py in <module>
----> 1 my_tuple[3] = 'tuple'

TypeError: 'tuple' object does not support item assignment

I already said that tuples are immutable, so changing them is not possible.

2.2 Why should I ever use a tuple?

Well, if lists can do all that tuples can do and more, why should I ever want to use them? There are several answers. First, precisely because they are immutable, they use less space. Secondly, the fact that you cannot change can also protect you from doing so. Third, tuples will be important later on when we discuss functions, as function arguments are passed as tuples.

Finally, there is a cool thing called tuple unpacking which gives you a very compact way of assigning variables.

my_tuple = ('Kant', 'Mill', 'Aristoteles')
deontology, utilitarianism, virtue = my_tuple
print(utilitarianism)
Mill

Neat, right?

3 Dictionaries

OK, after having quickly rushed through tuples we will now talk about the second most important ‘container’ data type after lists. Dictionaries are similar to lists with the key differences being that they are unordered and you cannot retrieve items (or values as they are called here) by their offset but need to use their key. These keys must be unique.

3.1 Creating a dictionary

my_dictionary = {'Neoclassical' : 'Marshall', 'NIE' : 'Williamson'}
print(my_dictionary)
{'Neoclassical': 'Marshall', 'NIE': 'Williamson'}

To create a dictionary, you have to use curly brackets. (We could of course also have created an empty dictionary.) Each entry in a dictionary consists of a key and an associated value after the colon. Our dictionary has two keys – the strings ‘Neoclassical’ and ‘NIE’ – as well as two values, the strings ‘Marshall’ and ‘Williamson’. Note that while we have used strings as keys in this example, you can use any of Python’s immutable types.

A second thing you might have noticed is that the order to entries changed when we printed it out. This is what I meant by calling dictionaries unordered.

As always, another way to create a dictionary is to convert another object by using the dict function. You can do this with:

  • a list of two-item lists,

  • a tuple of two-item tuples,

  • a list of two-item tuples,

  • a tuple of two-item lists,

  • a list of two-character strings, and

  • a tuple of two-character strings.

my_tuple = (('a', 'b'),('c', 'd'))
dict(my_tuple)
{'a': 'b', 'c': 'd'}
my_tuple = ('ab', 'cd')
dict(my_tuple)
{'a': 'b', 'c': 'd'}

What happens here?

my_tuple = ('ab', 'ac')
dict(my_tuple)
{'a': 'c'}

I already said that the keys must be unique. In this case, we would have assigned the strings 'b' and 'c' the same key, the string 'a'. This is not possible.

3.2 Getting an item

OK, so … since the elements of a dictionary are unordered how can we get an item? It is very easy, we have to use the key to obtain the corresponding value.

my_philosophers = {'Marx' : 'Karl',
                   'Wittgenstein' : 'Ludwig', 
                   'Kant' : 'Immanuel'}
print(my_philosophers)
{'Marx': 'Karl', 'Wittgenstein': 'Ludwig', 'Kant': 'Immanuel'}
my_philosophers['Kant']
'Immanuel'

Note however that this will throw you can error if the key does not exist.

my_philosophers['Hayek']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-49-d9e7cdad3670> in <module>()
----> 1 my_philosophers['Hayek']

KeyError: 'Hayek'

We can get around this by using the get method.

my_philosophers.get('Hayek', 'Not a philosopher.')
'Not a philosopher.'

3.3 Add or change items and combining dictionaries with update

Let’s get back to our philosophers.

my_philosophers
{'Kant': 'Immanuel', 'Marx': 'Karl', 'Wittgenstein': 'Ludwig'}

Say we want to add David Hume to our dictionary. Since dictionaries are mutable, we can do this. How you ask?

my_philosophers['Hume'] = 'David'
my_philosophers
{'Hume': 'David', 'Kant': 'Immanuel', 'Marx': 'Karl', 'Wittgenstein': 'Ludwig'}

In the same way, we can also change the value for an existing key.

my_philosophers['Marx'] = 'Carlos'
my_philosophers
{'Hume': 'David',
 'Kant': 'Immanuel',
 'Marx': 'Carlos',
 'Wittgenstein': 'Ludwig'}

That looks just ridiculous. We better change it back.

my_philosophers['Marx'] = 'Karl'

As with lists, we can also combine two dictionaries using the update method.

my_philosophers = {'Marx' : 'Karl', 'Kant' : 'Immanuel', 'Wittgenstein' : 'Ludwig'}
my_economists = {'Keynes' : 'John', 'Hayek' : 'Friedrich'}
my_philosophers.update(my_economists)
print(my_philosophers)
{'Marx': 'Karl', 'Kant': 'Immanuel', 'Keynes': 'John', 'Wittgenstein': 'Ludwig', 'Hayek': 'Friedrich'}

3.4 Deleting items by keys with del and all items with clear

As with lists you can delete individual items with the del statement.

my_philosophers = {'Marx' : 'Karl', 'Kant' : 'Immanuel', 'Wittgenstein' : 'Ludwig'}
print(my_philosophers)
{'Marx': 'Karl', 'Kant': 'Immanuel', 'Wittgenstein': 'Ludwig'}
del my_philosophers['Marx']
print(my_philosophers)
{'Kant': 'Immanuel', 'Wittgenstein': 'Ludwig'}

If you want to clear the whole dictionary, use the clear method.

my_philosophers.clear()
print(my_philosophers)
{}

3.5 Getting all keys and/or values, with keys, values, and items

Sometimes it is helpful to obtain a list (or iterable) of all values or keys of a dictionary. This can be accomplished using the keys and values methods. If you want all key-value pairs in a list of tuples, you need the method items.

my_philosophers = {'Marx' : 'Karl', 'Kant' : 'Immanuel', 'Wittgenstein' : 'Ludwig'}
my_philosophers.keys()
dict_keys(['Marx', 'Kant', 'Wittgenstein'])
list(my_philosophers.keys())
['Marx', 'Kant', 'Wittgenstein']
list(my_philosophers.values())
['Karl', 'Immanuel', 'Ludwig']
list(my_philosophers.items())
[('Marx', 'Karl'), ('Kant', 'Immanuel'), ('Wittgenstein', 'Ludwig')]

3.6 Membership by key with in

Well, testing for membership works similar to lists but you can only do it with keys

my_philosophers = {'Marx' : 'Karl', 'Kant' : 'Immanuel', 'Wittgenstein' : 'Ludwig'}
'Marx' in my_philosophers
True
'Karl' in my_philosophers
False

3.7 The caveat again: new dictionaries

Since dictionaries are mutable we have to be careful when we want to create a new dictionary and not only give it a second name. As with lists, this problem can be circumvented by using the copy method and the dict function.

my_philosophers = {'Marx' : 'Karl', 'Kant' : 'Immanuel', 'Wittgenstein' : 'Ludwig'}

my_philosophers2 = dict(my_philosophers)
my_philosophers3 = my_philosophers.copy()

del my_philosophers['Marx']

print(my_philosophers2)
print(my_philosophers3)
{'Marx': 'Karl', 'Kant': 'Immanuel', 'Wittgenstein': 'Ludwig'}
{'Marx': 'Karl', 'Kant': 'Immanuel', 'Wittgenstein': 'Ludwig'}

Sources

The structure of exposition was taken from Bill Lubanovic (2015): Introducing Python: Modern Computing in Simple Packages. O’Reilly: Sebastopol, CA.