In the coming chapters, we will take a look at the more complex objects that Python provides. We refer to these specific objects as data structures because these objects can contain other objects, unlike the data types we have seen before.
The #\color{#4271ae} {\mathtt{\text{list}}}# is Python's most versatile and useful data structure. A list in Python is an ordered collection of objects. The most common way to define a list is to specify a comma-separated sequence of objects enclosed in square brackets.
#\mathtt{[}\textit{object}_\textit{0},\textit{object}_\textit{1}, \dots, \textit{object}_\textit{n}\mathtt{]}#
#\color{#4271ae} {\mathtt{list}}\mathtt{(}\textit{iterable}\mathtt{)}#
When defining a list like this, the order of objects is saved. A list supports any type of object as individual element. Individual elements can also be complex objects, such as another #\color{#4271ae} {\mathtt{\text{list}}}#. The number of elements in a list can range from zero to as high as the memory of your computer allows.
Casting to #\mathtt{\color{#4271ae} {\text{list}}}# is quite a common operation. Casting with the #\mathtt{\color{#4271ae} {\text{list}}\text{()}}# function allows you to transform most iterables to a #\mathtt{\color{#4271ae} {\text{list}}}#. Usually, there's almost no loss of information when casting from any iterable to #\mathtt{\color{#4271ae} {\text{list}}}#, since #\mathtt{\color{#4271ae} {\text{list}}}# is an iterable too. Casting is mainly used to unlock the extensive functionality of the #\mathtt{\color{#4271ae} {\text{list}}}# object for a different iterable.
>>> list("Hello, world!")
|
['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!']
|
>>> list(range(10))
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
The #\color{#4271ae} {\mathtt{\text{list}}}# type supports a wide range of methods. In general the #\color{#4271ae} {\mathtt{\text{list}}}# type functions very similar to the #\color{#4271ae} {\mathtt{\text{str}}}# type, with one major difference; a list is mutable, while a string is immutable. In fact, all data types we have seen so far have been immutable.
An immutable object is an object whose state cannot be modified after it is created. In contrast, a mutable object can be modified after it is created.
What does this mean in practice? The value or state of immutable objects cannot be changed in-place. In other words, if we were to attempt to change the value of an immutable object, we would instead obtain a new modified copy of the object. If we were to change the state of a mutable object, we modify the object in-place; no copy is created.
For example, the #\color{#4271ae} {\mathtt{\text{int}}}# type is also immutable, to illustrate that we obtain a new object we use the #\mathtt{\color{#4271ae} {\text{id}}\text{()}}# function, which returns the unique identifier of an object.
>>> x = 1 >>> id(x)
|
140710358562592
|
>>> x += 1 >>> id(x)
|
140710358562624
|
Even when we perform addition on an #\color{#4271ae} {\mathtt{\text{int}}}#, a new object is created.
In contrast, when we add an element to a list, no new object is created, instead the object itself has changed; the identifier remains the same.
>>> l = [1, 2, 3] >>> id(l)
|
1491666326720
|
>>> l += [4] >>> id(l)
|
1491666326720
|
Since lists are so versatile, there's a lot of functionality to discuss. Take your time to go through all of the tabs.
It's no coincidence that the #\color{#4271ae} {\mathtt{\text{list}}}# type is so similar to the #\color{#4271ae} {\mathtt{\text{str}}}# type, both are iterables. But, remember that any method applied to a #\color{#4271ae} {\mathtt{\text{list}}}# changes the list in-place, since no new object is created, most #\color{#4271ae} {\mathtt{\text{list}}}# methods also don't return anything!
>>> s = "abc" >>> print(s.upper())
|
ABC
|
>>> l = ["a", "b", "c"] >>> print(l.append("d"))
|
None
|
>>> l
|
['a', 'b', 'c', 'd']
|
A list can be indexed exactly like a string. But, instead of returning a single character, the entire object at that index is returned. When using a slice, instead of a substring, a (sub)list is returned.
#\mathtt{[}# |
#\color{#718c00} {\mathtt{\text{"John"}}}# |
#,# |
#\color{#718c00} {\mathtt{\text{"Jane"}}}# |
#,# |
#\color{#718c00} {\mathtt{\text{"Mary"}}}# |
#\mathtt{]}# |
|
0 |
|
1 |
|
2 |
|
|
-3 |
|
-2 |
|
-1 |
|
For example, when indexing for the first element of a list, we obtain the entire first element.
>>> l = ["John", "Jane", "Mary"] >>> l[0]
|
'John'
|
Because lists can store more complex objects, we're able to obtain more complex elements within lists like lists or strings with a single expression.
Slicing a list returns a new list containing the sliced elements. Note that this is not an in-place operation, when nothing is assigned to the slice the original list isn't modified and a new list object is returned instead.
>>> l[1:3]
|
['Jane', 'Mary']
|
Since lists are mutable, we're also able to use indexing to modify the contents of a list. For example, changing the first element of #\mathtt{\text{l}}# from #\color{#F5871F} {\mathtt{\text{1}}}# to #\color{#F5871F} {\mathtt{\text{0}}}# using the index:
>>> l = [1, 2, 3] >>> l[0] = 0 >>> l
|
[0, 2, 3]
|
Now, through the use of slices, we can even change multiple elements at once.
>>> l[1:3] = [3, 4] >>> l
|
[0, 3, 4]
|
When modifying a list using slices, the range your slice describes doesn't have to be of the same length as the iterable you're trying to insert. Here, we replace a range containing one element with a list that is longer than said length.
>>> l[1:2] = [1, 2, 3] >>> l
|
[0, 1, 2, 3, 4]
|
Like strings, lists also support multiplication and addition. Only lists can be added to lists.
>>> [1, 2] + [3, 4]
|
[1, 2, 3, 4]
|
If you want to add one object to a list, enclose it in brackets and add it as a list.
>>> [1, 2] + [3]
|
[1, 2, 3]
|
Like strings, lists can only be multiplied by an integer, but since lists are more complex, you can get quite creative with list multiplication.
>>> [0] * 10
|
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
>>> mat = [[0] * 10] * 10 >>> mat
|
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
|
However, do note that when you are multiplying a list containing lists, you're actually multiplying references to those lists, so all the sublists you created are essentially the same object in memory. As a result, when you change one sublist you change all of them.
>>> mat[0][0] = 1 >>> mat
|
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
|
The comparison operators are also supported by lists. Since lists are sequences, comparisons work the same as they do for strings. Individual elements are compared until the comparison can be determined either #\color{#F5871F}{\mathtt{\text{True}}}# or #\color{#F5871F}{\mathtt{\text{False}}}#.
>>> [1, 2, 3] == [1, 2, 3]
|
True
|
>>> [1, 2, 3] > [1, 2, 4]
|
False
|
A #\color{#4271ae} {\mathtt{\text{list}}}# object supports a number of methods. The most common is the #\mathtt{\color{#4271ae} {\text{append}}\text{()}}# method, which appends elements to a list. The argument provided to #\mathtt{\color{#4271ae} {\text{append}}\text{()}}# is always appended as a single element, as opposed to the #\mathtt{\color{#4271ae} {\text{extend}}\text{()}}# method, which appends members of an iterable argument individually. Both #\mathtt{\color{#4271ae} {\text{append}}\text{()}}# and #\mathtt{\color{#4271ae} {\text{extend}}\text{()}}# always append new elements to the end of the list.
>>> l = [1, 2, 3] >>> l.append(4) >>> l
|
[1, 2, 3, 4]
|
>>> l.append([5, 6]) >>> l
|
[1, 2, 3, 4, [5, 6]]
|
>>> l.extend([7, 8, 9]) >>> l
|
[1, 2, 3, 4, [5, 6], 7, 8, 9]
|
The #\mathtt{\color{#4271ae} {\text{insert}}\text{()}}# method allows you to insert elements at a specified index. The first argument is the index and the second is the object to be inserted. Objects are always inserted before the provided index. If the index is not in range of the list the element will be added to either the start of the list when using negative indexing or the end of the list when using positive indexing.
>>> l = [1, 2, 3] >>> l.insert(2, 2.5) >>> l
|
[1, 2, 2.5, 3]
|
Now, lists support two main methods to remove elements, #\mathtt{\color{#4271ae} {\text{remove}}\text{()}}# removes elements by matching and #\mathtt{\color{#4271ae} {\text{pop}}\text{()}}# removes elements by index. #\mathtt{\color{#4271ae} {\text{pop}}\text{()}}# also returns the removed element. If no index is specified for #\mathtt{\color{#4271ae} {\text{pop}}\text{()}}# it will remove and return the last element of the list.
>>> l.remove(2.5) >>> l
|
[1, 2, 3]
|
The #\mathtt{\color{#4271ae} {\text{clear}}\text{()}}# method deletes all elements within the list.
Additionally, lists provide a few methods for utility, such as #\mathtt{\color{#4271ae} {\text{count}}\text{()}}#, #\mathtt{\color{#4271ae} {\text{index}}\text{()}}#, #\mathtt{\color{#4271ae} {\text{reverse}}\text{()}}#, and #\mathtt{\color{#4271ae} {\text{sort}}\text{()}}#. The #\mathtt{\color{#4271ae} {\text{count}}\text{()}}# method functions exactly like how it functions for a #\mathtt{\color{#4271ae}{\text{str}}}#, it counts the number of elements within the list that are the same as its argument. The #\mathtt{\color{#4271ae} {\text{index}}\text{()}}# method returns the index of the first occurence of its argument, if it can be found within the list. #\mathtt{\color{#4271ae} {\text{reverse}}\text{()}}# reverses the list, and #\mathtt{\color{#4271ae} {\text{sort}}\text{()}}# sorts the list in ascending order.
>>> l = [1, 2, 3] >>> l.count(1)
|
1
|
>>> l.reverse() >>> l
|
[3, 2, 1]
|
>>> l.sort() >>> l
|
[1, 2, 3]
|
Because lists are mutable, there's also the dedicated #\mathtt{\color{#4271ae} {\text{copy}}\text{()}}# method for making a copy of a list, since there's no other way to create a copy of a mutable object. As you can see, simply assigning the list to another variable doesn't create a new instance of the list, but a reference to the same instance.
>>> a = l >>> a.pop() >>> l
|
[1, 2]
|
>>> a = l.copy() >>> a.pop() >>> l
|
[1, 2]
|
As mentioned, lists are iterables and therefore also support a number of keywords and additional syntax.
Lists also support the #\color{#8959A8} {\mathtt{\text{del}}}# keyword for the deletion of elements. The main difference between #\color{#8959A8} {\mathtt{\text{del}}}# and the methods discussed above is that #\color{#8959A8} {\mathtt{\text{del}}}# supports removing multiple elements at once. However, the syntax does involve indices and slices as opposed to the other methods.
>>> l = [1, 2, 3, 4, 5] >>> del l[0] >>> l
|
[2, 3, 4, 5]
|
If we want to delete multiple elements at once we can combine #\color{#8959A8} {\mathtt{\text{del}}}# with a slice. If we want to delete all elements using #\color{#8959A8} {\mathtt{\text{del}}}# instead of #\mathtt{\color{#4271ae} {\text{clear}}\text{()}}#, we can use a slice that contains all of #\mathtt{\text{l}}#.
>>> del l[1:3] >>> l
|
[2, 5]
|
Like strings, lists also support the #\color{#8959A8} {\mathtt{\text{in}}}# keyword to test for membership of elements, however unlike strings we can only test for individual membership, and not for the membership of sublists.
>>> l = [1, 2, 3] >>> 2 in l
|
True
|
Like any other iterable, lists also have a very convenient syntax for #\color{#8959A8} {\mathtt{\text{for}}}# loops. The #\color{#8959A8} {\mathtt{\text{for}}}# loop will return one element of the list at every iteration.
>>> l = [1, 2, 3] >>> for elem in l: >>> ... print(elem)
|
1 2 3
|
Because #\color{#8959A8} {\mathtt{\text{for}}}# loops are used in combination with lists so often, especially for building lists, this specific use has received a shorthand syntax called a list comprehension. List comprehensions allow you to build lists quickly by writing a special for loop inside square brackets. The basic syntax is #\mathtt{\text{[}\text{x}\color{#8959A8}{\text{ for }}\text{x}\color{#8959A8}{\text{ in }}\text{y}\text{]}}#, where #\mathtt{\text{x}}# is the element, and #\mathtt{\text{y}}# the iterable.
>>> [num for num in range(10)]
|
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
This syntax also allows you to write an expression for the elements returned in the list comprehension.
>>> [num**2 for num in range(10)]
|
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
|
You can even specify conditions that have to be satisfied for elements to be included in the resulting list.
>>> [num for num in range(10) if num > 6]
|
[7, 8, 9]
|
List comprehensions are extremely useful, but keep readability in mind, if a list comprehension is getting too complex, simply use a normal #\color{#8959A8} {\mathtt{\text{for}}}# loop instead.