Table of Contents
- How Indexing and Slicing Are Used in Lists
- What is the Difference Between Mutability and Immutability?
- How to Write Different List Methods in Python
Welcome back to another article in my Intro to Programming series. Today I’ll talk about lists in Python.
Lists are one of the most powerful data types in Python. They’re used all the time, as many things in the real world come in lists.
By way of definition, lists are ordered sequences that contain objects with various data types. If you remember, strings are also ordered sequences, but they contain only characters. Lists can contain strings, integer numbers, floating-point numbers, and pretty much every other object of any data type . . . Including, lists!
Lists can actually contain other lists and those lists can themselves contain other lists, or a combination of lists and strings.
When I talked about strings previously, you saw that they are written using single or double quotes. By contrast, lists are written using square brackets. All of the objects inside a list are separated by commas. Here you can see an example of a fairly complex list:
It contains the string 'i am a string,' the integer 5, the float 10.0, and a list that contains just the string 'hi.' I colored the square brackets in green and the strings in red. These are the same color codes you might see in a Jupyter notebook. But if you use a programming text editor or the interactive command line of the Python interpreter, you might see different colors.
- For more on Jupyter Notebooks > How to Write and Run Code in Jupyter Notebooks
- For more about the command line > Intro to Programming: How to Use the Command Line (Part 1)
Hopefully this example of a complex list clearly shows what a list can look like. However, lists don't have to necessarily contain objects that have different data types. They can also just contain integers, or they can just contain strings. Or, they can just contain other lists, or even dictionaries and tuples. Lists can even be empty and contain nothing at all. In fact, lists are super flexible, which makes them powerful.
How Indexing and Slicing Are Used in Lists
Another important feature of lists is that they support indexing and slicing. You first encountered these concepts when I talked about strings:
- Indexing allows you to return a character located at a certain index, or location, in a string.
- Slicing allows you to get a substring from a string by starting at a given index and ending at a given index.
The ideas behind list indexing and slicing are similar. In fact, the notations are exactly the same, as you'll see in a moment. With lists, indexing allows you to return an object located at a certain index, or location, in the list, while slicing allows you to return a sub-list of objects from the main list.
Example 1: How to Make Lists in Python
Let's open a new Jupyter notebook and explore lists in more detail.
I'll start by defining a variable called numbers that stores the list [1,2,3,4,5]. If I output numbers, you see that it's a list. I can also use the type function to double check the data type of numbers and see that it's a list. Similar to strings, lists can also be printed using the print function.
# this creates the variable called numbers and stores the list in it numbers = [1,2,3,4,5] # this returns type list type(numbers) # this prints the list stored in the variable numbers print(numbers)
Another useful function that you learned using strings was len, which is short for length. Previously you saw the len()function print the length of a string. But in fact, the len() function will give you the length of any sequence, including the length of lists.
Why? Because a list is just an ordered sequence of objects.
Let's test that out: when I input len(numbers) into my notebook, I get a 5.
# this returns the length of the list, which is 5 len(numbers)
I mentioned earlier that lists can also be empty. That is, they don't necessarily need to contain any objects. When lists don't have any objects inside them, you just write the square brackets, like so: empty_list = .
# this creates an empty list and stores it in the variable empty_list empty_list = 
And you can check that empty_list is actually a list by running type(empty_list). Also, if you check the length using the len function by writing len(empty_list), you'll get 0. This makes sense, since this list has no objects inside it.
# this returns type list type(empty_list) # this returns the length of empty_list, which is 0 len(empty_list)
Let's create a more interesting list that contains objects of different data types. Say you have a person and you want to store their eye color, their height in feet, their weight in pounds, and their favorite cities for vacation. So far in our intro to Python you haven’t had at your disposal a data type that can hold all of that information in a single place. Instead, perhaps you would have defined a variable for each of these attributes. But with lists, holding all of that information in one place is easy.
To do so, you can define one variable. Let's call it person. We'll make person = ['blue', 5.7, 120.5, ['Amsterdam', 'Barcelona']]. So, this person has blue eyes, is 5.7 feet tall, weighs 120.5 pounds, and their two top cities for vacation are Amsterdam and Barcelona.
# this creates a list with objects of varying data types person = ['blue', 5.7, 120.5, ['Amsterdam', 'Barcelona']]
You can see already that lists are powerful because they allow you to hold all of this data neatly in one place. Otherwise, you probably would have had to use several variables to do so, which can get messy.
Now that you have this variable storing the attributes of our blue-eyed Barcelona-loving person, your program might need to access a specific attribute. Say that your program needs to know the color of your person's eyes. To find that information, we can use indexing.
If you look at the list, you see that the first object in the list is eye color. So, you can write person. When you run that, you get ‘blue’. You use 0 here because, in programming, you always start counting indexes at 0.
# this returns the list item at position 0, which is 'blue' person
But what if you want to get height and weight?
These are two objects from the list. Recalling your exercises with strings, you can use slicing and write person[1:3]. Here, you slice starting at index 1 because the height is at index 1, and you'll need to go up to index 3 because slicing returns all the objects up to, but not including, the last index.
In this case your last index is 3. Your notation will return the objects in the list that are at indexes 1 and 2, which are height and weight.
# this returns the height and weight, 5.7 and 120.5 person[1:3]
Here's an even more interesting question: How do you get the name of the second favorite vacation city for this person?
To start, you know that you can return the list of cities within your larger list by looking at the object at index 3. Let's write that out, inputting person will return a list that contains the strings 'Amsterdam' and 'Barcelona'.
# this returns 'Amsterdam' and 'Barcelona' person
So, how do you get from here to returning only 'Barcelona'? Because these cities are stored in a list itself, you can use indexing again.
In this list, ‘Barcelona’ is at index 1. So you write person and you get 'Barcelona'.
# this returns only 'Barcelona' person
This might look a bit strange at first, but if you take a moment and break it down, it will make sense. In this example, person is a list that has two strings: 'Amsterdam' and 'Barcelona'. If you call from this list the object at index 1, you'll get 'Barcelona'. This is called nested indexing and you'll see more of it, so take some time to practice using it. Define your own nested lists and practice indexing into them.
And if you need to refresh your memory about indexing and slicing, revisit our Intro to Programming article where we discussed string indexing and slicing. List indexing and slicing works in exactly the same way.
Example 2: How to Merge Two Lists in Python
Now let's say you have another list that stores more information about this person. Assume you got this data from a different source, so it ends up in a different list. Let's call this list ids and let's make ids = ['111-00-0000', 'S000001'].
The first item in the list looks like a social security number. The second one could be a driver's license number.
# this creates the variable of type list called ids ids = ['111-00-0000', 'S000001']
In this case, you'd like to merge our two lists. As a general rule of thumb in programming, limiting the number of variables used is always a good idea. The fewer the variables, the easier it is to read and understand your code.
Consider a program with 100 variables. How can any human to keep track of all of them while reading the program? So generally, you want to keep the number of variables as low as possible. In my example, the ids list contains information about the same person as in the person list, so I should combine them.
You saw while you were learning about strings that Python overloaded the plus operator (in other words, re-purposed it) to also be used to merge two strings. This practice is called string concatenation.
Does concatenation with the + operator work for lists, too? Let's give it a shot.
If you type person + ids, you get the combined list of attributes.
# this combines the lists person and ids person + ids
But what happens if you print the value stored in the variable named person now?
# this prints the list person print(person)
You will see that it hasn't changed. Why not? Because you didn't reassign it.
By typing person + ids, you simply printed the result of merging the list of person attributes with the list of IDs. What you didn't do is store that result back into the person variable, so it didn’t change. In order to update the person variable to include your ID numbers, you need to use variable assignment.
See the following use of variable assignment: person = person + ids.
# this stores a new value into the variable person person = person + ids
If you output person now, you get the complete combined list of attributes. The key takeaway from this example is that the + operator doesn't modify the lists. If you want to change a variable, you must use variable assignment.
What is the Difference Between Mutability and Immutability?
I've talked a lot about similarities between lists and strings in Python, but let’s talk now about one important difference between them: their mutability and immutability.
Let's jump into Jupyter notebooks and explore these concepts.
Remember the blue-eyed, travel-loving person from the last section? Let's say that they actually went on a lovely trip to Barcelona, had a great time, ate lots of yummy food, and, as a result, put on a bit of weight. Let's say their weight is now 124 pounds. How do you update our list to reflect that?
Well, one silly way is to redefine the variable with the new value. But why go through all that typing? What if you had 100 attributes to update? That would be annoying.
Thankfully, lists have an easy way to change the value of a given object. Let me show you.
First, you start by typing out the index notation for the object inside the list that you want to update. In this case, weight is at index 2. So you write person:
# this returns the value at index 2, which is 120.5 person
If you just type that notation, Python returns the weight. But if you want to change the value of this object, you have to use variable assignment.
To do so, you type person = 124. Now, if you output the whole list again, you'll see that the weight is updated to 124.
# this updates the value at index 2 to 124 person = 124 # this outputs the list with the updated values print(person)
How did that work? Recall our analogy about computer memory as little memory boxes in my article about variables in Python.
You can think of lists as ordered collections of little memory boxes. Into each box, you put one of the objects in the list and you go in order. In the first box, which is at index 0, you'll store the string 'blue.' In the second box, which is at index 1, you'll store the height, which is the floating-point number 5.7. You'll follow this pattern until you go through your entire list.
While these boxes are not named individually, as you've seen with other variables, they are accessible via their index. You can call them via index because their order doesn't change (which is why lists are called “ordered sequences”). When you type out person, what you're doing is looking into the collection of memory boxes that store the list. Specifically, you're looking inside the box that is located at index 2, and then you can either get the value that's inside that box, or you can use variable assignment to store another value inside of it.
You might be wondering if you can do the same with strings. In other words, can you reach inside a string and change one of the characters?
Let's try it. I’ll define a variable called my_string and store the phrase 'hello world' in it.
# this creates a variable and stores a string in it my_string = 'hello world'
To check to see the character at index 0, I use my_string and I get an 'h', as I'd expect.
# this returns the character at index 0, which is 'h' my_string
Now, let's try to change the value of that character to a capital 'H'. I type my_string = 'H'.
But now, I get an error.
# an attempt to store an 'H' into index 0 of the string, which returns a TypeError my_string = 'H' ----------------------------------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-20-653a8eb96e00> in <module>() ----> 1 my_string = 'H' TypeError: 'str' object does not support item assignment
The answer to your question is no: strings do not support item assignment.
With strings, you cannot change individual characters thanks to a property called “immutability”. In other words, strings cannot mutate. Lists, however, and as you just saw, can mutate. In programmer speak, lists are “mutable."
Remember when I used the upper function to change all of the characters inside a string to uppercase? That might seem contradictory to the immutability of strings. But what happened behind the scenes using the upper function is that the whole string variable was reassigned to the upper-case version of the string.
That's the equivalent of typing my_string = 'HELLO WORLD' into Python. If we print my_string after doing that, you'll see that it's all in uppercase.
# this reassigns a new value to the variable my_string = 'HELLO WORLD' # in Jupyter notebook, this outputs the new string in the variable, which is 'HELLO WORLD' my_string
This is an important concept and one that you have to be aware of. Strings are immutable, but lists are mutable. Later in the course, you'll learn about one more data type, which, like strings, is immutable (spoiler alert: it's the tuple).
How to Write Different List Methods in Python
In this section I'll talk about a few useful list methods: append, pop, sort, and reverse.
- Remember: methods are like functions, except that they're attached to an object.
The object, in this case, is a list.
Let's create a new list and store numbers in it. I’ll input into my notebook numbers = [1,2,3,4,5].
# this creates a new list numbers = [1,2,3,4,5]
To see a list of methods available for my list, I can again write the name of my list variable, numbers, in this case, and then type period and hit the tab key. This gives me a bunch of them, including append, clear, copy, and others.
How to Use .append()
I'll go over append first. As the name implies, append allows you to append, or add, items to a list. In my example, if I input numbers.append(6)and look at the updated list, you'll see that numbers now contains the integers [1,2,3,4,5,6].
# this appends a number to the end of a list numbers.append(6) # this prints the list, with the new value appended. The output is [1, 2, 3, 4, 5, 6] print(numbers)
One thing to remember is that append adds the item at the end of the list. You should also note that it actually changes the list.
The method append didn't print the original list, with 6 added at the end. Instead, it actually added 6 to the list and modified the list.
I'll add a few more. Now I’ll type numbers.append(7) and again numbers.append(8). Printing the list, you'll see that 7 and 8 are added at the end.
# this appends two new numbers to the end of the list numbers.append(7) numbers.append(8) # this prints the list with the new values added. The output is [1, 2, 3, 4, 5, 6, 7, 8] print(numbers)
How to Use .pop()
What if you want to remove elements from a list? The opposite of the append method is the pop method. I'll input numbers.pop() and see what I get:
# this 'pops' (removes) the last value in the list, which is 8 numbers.pop() # this prints the remaining items in numbers. The output is [1, 2, 3, 4, 5, 6, 7] print(numbers)
Notice a few things: First, because I didn’t specify a parameter, pop has removed the last item in the list. Second, calling the pop method actually returned the item that was removed. And third, taking a look at our list now, you'll see that it no longer has the integer 8 inside of it.
The nice thing about pop, is that by returning the item that was removed, it allows me to store it somewhere. Let me show you.
I can define a variable num and set it to equal the result of numbers.pop(). Now, if I output num, I see that it's the integer 7.
# this defines a new variable and stores in it the last value in numbers num = numbers.pop() # this prints the value stored in the variable, which is 7 print(num)
This makes sense, because 7 was the last item in the list, or the one that got removed.
If I print the list now, I see that it has the integers [1,2,3,4,5,6] because 7 was removed.
# this prints the list after popping the last value. The output is [1, 2, 3, 4, 5, 6] print(numbers)
If you change your mind while using pop, and want to add an integer back, you can use append.
In our example above, instead of using 7, you can actually use the num variable as a parameter: numbers.append(num). Now, if I print the numbers list, you'll see that 7 is back at the end:
# this appends the num value back to the list numbers.append(num) # this prints the value of numbers, which is [1, 2, 3, 4, 5, 6, 7] print(numbers)
The item you may want to remove might not always be the last one in the list, however. In these cases, the pop method also allows you to specify the index of the item we want to remove.
So, for example, if I type numbers.pop(0), this command will remove the item at index 0, which is the integer 1. We can verify this by printing the list and checking that the integer 1 is missing:
# this removes the item at index 0, which is 1 numbers.pop(0) # this prints the new value of numbers, which is [2, 3, 4, 5, 6, 7] print(numbers)
If you remember reverse indexing in strings, you probably guessed that, by default, the pop method assumes an index of -1 when we call it without any parameters. The item at index -1 is the last item in the list.
I can check that quickly using numbers.pop(-1). You should see that the last item in the list was “popped” off (in this case the integer number 7). If I print the list to verify, you see that 7 is no longer on it.
# this removes the value at index -1, which is 7 numbers.pop(-1) # this prints the new value of numbers, which is [2, 3, 4, 5, 6] numbers
How to Use .sort()
Two other super useful list methods that I'll cover next are sort and reverse. As the names suggest, these methods help you sort items in ascending or descending (reverse) order.
Let's go back to the numbers list, but this time I'll set it to contain randomly ordered integers. I’ll input numbers = [6,3,8,9,3,0]. Now I can call the sort method by writing numbers.sort():
# this stores a new list into the variable numbers = [6,3,8,9,3,0] # this sorts the elements in numbers in ascending order numbers.sort()
Note that the sort method doesn't take any parameters. Also, when running sort, you won't see any output.
Instead, you must print the contents of your list to see that the integers are sorted in ascending order. The sort method modifies the list in place by ordering the objects inside it:
# this prints the new value in the variable after using the sort method, which returns [0, 3, 3, 6, 8, 9] numbers
The sort method also works with lists of strings.
Let’s create another list with letters = ['z', 'a', 'e', 'c', 'd', 't']. If I write letters.sort() again, you again don't see any output. But, what happened is that the objects in the list—in this case strings that contain a single character—are now sorted alphabetically:
# this creates a new list letters = ['z', 'a', 'e', 'c', 'd', 't'] # this runs the sort method on the list letters.sort() # this prints the new value in the list after using the sort method, which is ['a', 'c', 'd', 'e', 't', 'z'] print(letters)
The strings don't have to be single character for this to work, of course. You can even sort names, for example.
Let's create a new variable and call it countries and set countries = ['USA', 'France', 'Japan', 'Germany']. Now if I write countries.sort() and output the value stored in the list, I get the list of countries, but listed in alphabetical order:
# this creates a new list countries = ['USA', 'France', 'Japan', 'Germany'] # this sorts the list countries.sort() # this prints the new value of the list, which is ['France', 'Germany', 'Japan', 'USA'] print(countries)
How to Use .reverse()
If you want to sort objects in descending order, or in reverse alphabetical order, you can use the reverse method. Like the sort method, the reverse method also doesn't take any parameters. It also modifies the list in place.
Let's see it in action and reverse all of the lists I defined above and sorted alphabetically. Starting with the list of countries, I'll now write countries.reverse(). Outputting the countries, you'll see that they’re now listed in reverse alphabetical order:
# this reverses the order of elements in the countries list countries.reverse() # this prints the new value of countries after using the reverse method, which is ['USA', 'Japan', 'Germany', 'France'] print(countries)
Trying the same with letters, I'll write letters.reverse() and see that the letters are now also in reverse alphabetical order:
# this reverses the elements in the letters list letters.reverse() # this prints the new value of letters after using the reverse method, which is ['z', 't', 'e', 'd', 'c', 'a'] print(letters)
And finally, reverse also works with numbers.
Inputting numbers.reverse() will give you the same list of integer numbers, but in descending order:
# this reverses the order of elements in numbers numbers.reverse() # this prints the new value numbers after using the reverse method, which is [9, 8, 6, 3, 3, 0] numbers
That's it for lists!
As you saw, lists are ordered sequences of objects, and these objects can be of different data types. You learned that indexing and slicing also apply to lists. You also learned how to concatenate lists and get the length of lists. You modified objects in your lists by appending objects and removing objects. Finally, you learned about sorting.
Lists are widely used and important, so spend some time to get comfortable with them. I encourage you to go back and re-read the article on indexing and slicing if needed.
In the next article we'll look at another powerful data type: the dictionary.