Working with Lists in Python
Tags: python, tutorial, beginners, programming
Published: 2022-04-20 00:14:12

## Looping Through an Entire List

Looping allows you to take the same action, or set of actions, with every item in a list. As a result, you’ll be able to work efficiently with lists of any length, including those with thousands or even millions of items.

You’ll often want to run through all entries in a list, performing the same task with each item. For example, in a game you might want to move every element on the screen by the same amount, or in a list of numbers you
might want to perform the same statistical operation on every element. Or perhaps you’ll want to display each headline from a list of articles on a website. When you want to do the same action with every item in a list, you can use Python’s *for* loop.

Let’s say we have a list of magicians’ names, and we want to print out each name in the list. We could do this by retrieving each name from the list individually, but this approach could cause several problems. For one, it would be repetitive to do this with a long list of names. Also, we’d have to change our code each time the list’s length changed. A for loop avoids both of these issues by letting Python manage these issues internally.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(user)
```

```CMD
Ahmed
Mohammed
Ali
```

This line `for user in users:` tells Python to pull a name from the list *users*, and associate it with the variable *user*.

### A Closer Look at Looping

The concept of looping is important because it’s one of the most common ways a computer automates repetitive tasks. For example, in a simple loop like we used, Python initially reads the first line of the loop:

```Python
for user in users:
```

This line tells Python to retrieve the first value from the list *users* and associate it with the variable *user*. This first value is 'Ahmed'. Python then reads the next line:

```Python
    print(user)
```

Python prints the current value of *user*, which is still 'Ahmed'. Because the list contains more values, Python returns to the first line of the loop:

```Python
for user in users:
```

Python retrieves the next name in the list, 'Mohammed', and associates that value with the variable *user*. Python then executes the line:

```Python
    print(user)
```

Python prints the current value of magician again, which is now 'Mohammed'.

Python repeats the entire loop once more with the last value in the list, 'Ali'. Because no more values are in the list, Python moves on to the next line in the program. In this case nothing comes after the for loop, so the program simply ends.

When you’re using loops for the first time, keep in mind that the set of steps is repeated once for each item in the list, no matter how many items are in the list. If you have a million items in your list, Python repeats these steps a million times—and usually very quickly.

Also keep in mind when writing your own for loops that you can choose any name you want for the temporary variable that will be associated with each value in the list. However, it’s helpful to choose a meaningful name that represents a single item from the list.

### Doing More Work Within a for Loop

You can do just about anything with each item in a for loop. Let’s build on the previous example by printing a message to each user.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(f"Hello {user}")
```

```CMD
Hello Ahmed
Hello Mohammed
Hello Ali
```

You can also write as many lines of code as you like in the *for* loop. Every indented line following the line `for user in users:` is considered inside the loop, and each indented line is executed once for each value in the list. Therefore, you can do as much work as you like with each value in the list.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(f"Hello {user}")
    print(f"How are you, {user}?\n")
```

```CMD
Hello Ahmed
How are you, Ahmed?

Hello Mohammed
How are you, Mohammed?

Hello Ali
How are you, Ali?
```

You can use as many lines as you like in your *for* loops. In practice you’ll often find it useful to do a number of different operations with each item in a list when you use a *for* loop.

### Doing Something After a for Loop

What happens once a for loop has finished executing? Usually, you’ll want to summarize a block of output or move on to other work that your program must accomplish.

Any lines of code after the *for* loop that are not indented are executed once without repetition.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(f"Hello {user}")
    print(f"How are you, {user}?\n")
    
print("Thanks Everyone.")
```

```CMD
Hello Ahmed
How are you, Ahmed?

Hello Mohammed
How are you, Mohammed?

Hello Ali
How are you, Ali?

Thanks Everyone.
```

When you’re processing data using a *for* loop, you’ll find that this is a good way to summarize an operation that was performed on an entire data set. For example, you might use a for loop to initialize a game by running through a list of characters and displaying each character on the screen. You might then write some additional code after this loop that displays a Play Now button after all the characters have been drawn to the screen.

### Avoiding Indentation Errors

Python uses indentation to determine how a line, or group of lines, is related to the rest of the program. In the previous examples, the lines that printed messages to individual user were part of the *for* loop because they were indented. Python’s use of indentation makes code very easy to read. Basically, it uses whitespace to force you to write neatly formatted code with a clear visual structure. In longer Python programs, you’ll notice blocks of code indented at a few different levels. These indentation levels help you gain a general sense of the overall program’s organization.

As you begin to write code that relies on proper indentation, you’ll need to watch for a few common indentation errors. For example, people sometimes indent lines of code that don’t need to be indented or forget to indent lines that need to be indented. Seeing examples of these errors now will help you avoid them in the future and correct them when they do appear in your own programs.

#### Forgetting to Indent

Always indent the line after the for statement in a loop. If you forget, Python will remind you.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
print(f"Hello {user}")
```

```CMD
IndentationError: expected an indented block
```

You can usually resolve this kind of indentation error by indenting the line or lines immediately after the *for* statement.

#### Forgetting to Indent Additional Lines

Sometimes your loop will run without any errors but won’t produce the expected result. This can happen when you’re trying to do several tasks in a loop and you forget to indent some of its lines.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(f"Hello {user}")
print(f"How are you, {user}?\n")
```

```CMD
Hello Ahmed
Hello Mohammed
Hello Ali
How are you, Ali?
```

This is a *logical error*. The syntax is valid Python code, but the code does not produce the desired result because a problem occurs in its logic. If you expect to see a certain action repeated once for each item in a list and it’s executed only once, determine whether you need to simply indent a line or a group of lines.

#### Indenting Unnecessarily

If you accidentally indent a line that doesn’t need to be indented, Python informs you about the unexpected indent.

```Python
message = "Hello Python world!"
print(message)
```

```CMD
IndentationError: unexpected indent
```

You can avoid unexpected indentation errors by indenting only when you have a specific reason to do so.

If you accidentally indent code that should run after a loop has finished, that code will be repeated once for each item in the list. Sometimes this prompts Python to report an error, but often this will result in a *logical error*.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users:
    print(f"Hello {user}")
    print(f"How are you, {user}?\n")
    
    print("Thanks Everyone.")
```

```CMD
Hello Ahmed
How are you, Ahmed?

Thanks Everyone.
Hello Mohammed
How are you, Mohammed?

Thanks Everyone.
Hello Ali
How are you, Ali?

Thanks Everyone.
```

This is another *logical error*, similar to the one in “Forgetting to Indent Additional Lines”. Because Python doesn’t know what you’re trying to accomplish with your code, it will run all code that is written in valid syntax. If an action is repeated many times when it should be executed only once, you probably need to unindent the code for that action.

#### Forgetting the Colon

The colon at the end of a for statement tells Python to interpret the next line as the start of a loop.



If you accidentally forget the colon, as shown at u, you’ll get a syntax error because Python doesn’t know what you’re trying to do. Although this is an easy error to fix, it’s not always an easy error to find. You’d be surprised by the amount of time programmers spend hunting down single character errors like this. Such errors are difficult to find because we often just see what we expect to see.

```Python
users = ['Ahmed', 'Mohammed', 'Ali']
for user in users
    print(f"Hello {user}")
```

```CMD
SyntaxError: expected ':'
```

## Making Numerical Lists

Many reasons exist to store a set of numbers. For example, you’ll need to keep track of the positions of each character in a game, and you might want to keep track of a player’s high scores as well. In data visualizations, you’ll almost always work with sets of numbers, such as temperatures, distances, population sizes, or latitude and longitude values, among other types of numerical sets.

Lists are ideal for storing sets of numbers, and Python provides a variety of tools to help you work efficiently with lists of numbers. Once you understand how to use these tools effectively, your code will work well even when your lists contain millions of items.

### Using the range() Function

Python’s *range()* function makes it easy to generate a series of numbers. For example, you can use the *range()* function to print a series of numbers like this:

```Python
for value in range(1, 5):
    print(value)
```

```CMD
1
2
3
4
```

Although this code looks like it should print the numbers from 1 to 5, it doesn’t print the number 5.

In this example, *range()* prints only the numbers 1 through 4. This is another result of the off-by-one behavior you’ll see often in programming languages. The *range()* function causes Python to start counting at the first value you give it, and it stops when it reaches the second value you provide. Because it stops at that second value, the output never contains the end value, which would have been 5 in this case. To print the numbers from 1 to 5, you would use `range(1, 6)`.

You can also pass *range()* only one argument, and it will start the sequence of numbers at 0. For example, `range(6)` would return the numbers from 0 through 5.

```Python
for value in range(6):
    print(value)
```

```CMD
0
1
2
3
4
5
```

### Using range() to Make a List of Numbers

If you want to make a list of numbers, you can convert the results of *range()* directly into a list using the *list()* function. When you wrap *list()* around a call to the *range()* function, the output will be a list of numbers.

```Python
numbers = list(range(1, 6))
print(numbers)
```

```CMD
[1, 2, 3, 4, 5]
```

We can also use the *range()* function to tell Python to skip numbers in a given range. If you pass a third argument to *range()*, Python uses that value as a step size when generating numbers.

```Python
even_numbers = list(range(2, 11, 2))
print(even_numbers)
```

```CMD
[2, 4, 6, 8, 10]
```

You can create almost any set of numbers you want to using the *range()* function. For example, consider how you might make a list of the first 10 square numbers (that is, the square of each integer from 1 through 10). In Python, two asterisks `**` represent exponents.

```Python
squares = []

for value in range(1, 11):
    square = value **2
    squares.append(square)
    
print(squares)
```

```CMD
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

To write this code more concisely, omit the temporary variable square and append each new value directly to the list:

```Python
squares = []

for value in range(1, 11):
    squares.append(value **2)
    
print(squares)
```

You can use either of these two approaches when you’re making more complex lists. Sometimes using a temporary variable makes your code easier to read; other times it makes the code unnecessarily long. Focus first on writing code that you understand clearly, which does what you want it to do. Then look for more efficient approaches as you review your code.

## Simple Statistics with a List of Numbers

A few Python functions are helpful when working with lists of numbers. For example, you can easily find the minimum, maximum, and sum of a list of numbers.

```PowerShell
>>> digits = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> min(digits)
0
>>> max(digits)
9
>>> sum(digits)
45
```

## List Comprehensions

The approach described earlier for generating the list *squares* consisted of using three or four lines of code. A *list comprehension* allows you to generate this same list in just one line of code. A *list comprehension* combines the *for* loop and the creation of new elements into one line, and automatically
appends each new element.

The following example builds the same list of *square* numbers you saw earlier but uses a *list comprehension*:

```Python
squares = [value **2 for value in range(1, 11)]
print(squares)
```

```CMD
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

It takes practice to write your own *list comprehensions*, but you’ll find them worthwhile once you become comfortable creating ordinary lists. When you’re writing three or four lines of code to generate lists and it begins to feel repetitive, consider writing your own list comprehensions.

## Working with Part of a List

We learned how to access single elements in a list, and how to work through all the elements in a list.
You can also work with a specific group of items in a list, which Python calls a *slice*.

### Slicing a List

To make a *slice*, you specify the index of the first and last elements you want to work with. As with the *range()* function, Python stops one item before the second index you specify. To output the first three elements in a list, you would request indices 0 through 3, which would return elements 0, 1, and 2.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[0:3])
```

```CMD
['Ahmed', 'Mohammed', 'Ali']
```

You can generate any subset of a list. For example, if you want the second, third, and fourth items in a list, you would start the *slice* at index 1 and end at index 4.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[1:4])
```

```CMD
['Mohammed', 'Ali', 'Omar']
```

If you omit the first index in a *slice*, Python automatically starts your *slice* at the beginning of the *list*.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[:4])
```

```CMD
['Ahmed', 'Mohammed', 'Ali', 'Omar']
```

Without a starting index, Python starts at the beginning of the list. 

A similar syntax works if you want a slice that includes the end of a list. For example, if you want all items from the third item through the last item, you can start with index 2 and omit the second index.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[2:])
```

```CMD
['Ali', 'Omar', 'Hassan']
```

This syntax allows you to output all of the elements from any point in your list to the end regardless of the length of the list. Recall that a negative index returns an element a certain distance from the end of a list; therefore, you can output any slice from the end of a list. For example, if we want to output the last three names, we can use the *slice* `names[-3:]`.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[-3:])
```

```CMD
['Ali', 'Omar', 'Hassan']
```

This prints the names of the last three names and would continue to work as the list of players changes in size.

You can include a third value in the brackets indicating a slice. If a third value is included, this tells Python how many items to skip between items in the specified range.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']
print(names[0:4:2])
```

```CMD
['Ahmed', 'Ali']
```

### Looping Through a Slice

You can use a slice in a for loop if you want to loop through a subset of the elements in a list.

```Python
names = ['Ahmed', 'Mohammed', 'Ali', 'Omar', 'Hassan']

print("Here is the first three names in the list:")
for name in names[:3]:
    print(name.lower())
```

```CMD
Here is the first three names in the list:
ahmed
mohammed
ali
```

Instead of looping through the entire list, Python loops through only the first three names.

Slices are very useful in a number of situations. For instance, when you’re creating a game, you could add a player’s final score to a list every time that player finishes playing. You could then get a player’s top three scores by sorting the list in decreasing order and taking a slice that includes just the first three scores. When you’re working with data, you can use slices to process your data in chunks of a specific size. Or, when you’re building a web application, you could use slices to display information in a series of pages with an appropriate amount of information on each page.

## Copying a List

Often, you’ll want to start with an existing list and make an entirely new list based on the first one. Let’s explore how copying a list works and examine one situation in which copying a list is useful.

To copy a list, you can make a slice that includes the entire original list by omitting the first index and the second index `[:]`. This tells Python to make a slice that starts at the first item and ends with the last item, producing a copy of the entire list.

For example, imagine we have a list of our favorite foods and want to make a separate list of foods that a friend likes. This friend likes everything in our list so far, so we can create their list by copying ours.

```Python
my_foods = ['pizza', 'pasta', 'cake']
friend_foods = my_foods[:]

print("My favorite foods are:")
print(my_foods)

print("\nMy friend's favorite foods are:")
print(friend_foods)
```

```CMD
My favorite foods are:   
['pizza', 'pasta', 'cake']

My friend's favorite foods are:
['pizza', 'pasta', 'cake']
```

To prove that we actually have two separate lists, we’ll add a new food to each list and show that each list keeps track of the appropriate person’s favorite foods.

```Python
my_foods = ['pizza', 'pasta', 'cake']
friend_foods = my_foods[:]

my_foods.append('ice cream')
friend_foods.append('rice')

print("My favorite foods are:")
print(my_foods)

print("\nMy friend's favorite foods are:")
print(friend_foods)
```

```CMD
My favorite foods are:
['pizza', 'pasta', 'cake', 'ice cream']

My friend's favorite foods are:
['pizza', 'pasta', 'cake', 'rice']
```

If we had simply set *friend_foods* equal to *my_foods*, we would not produce two separate lists. For example, here’s what happens when you try to copy a list without using a *slice*.

```Python
my_foods = ['pizza', 'falafel', 'carrot cake']

# This doesn't work:
friend_foods = my_foods

my_foods.append('ice cream')
friend_foods.append('rice')

print("My favorite foods are:")
print(my_foods)

print("\nMy friend's favorite foods are:")
print(friend_foods)
```

```CMD
My favorite foods are:
['pizza', 'falafel', 'carrot cake', 'ice cream', 'rice']

My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake', 'ice cream', 'rice']
```

Instead of storing a copy of *my_foods* in *friend_foods*, we set *friend_foods* equal to *my_foods*. This syntax actually tells Python to associate the new variable *friend_foods* with the list that is already associated with *my_foods*, **so now both variables point to the same list**. As a result, when we add 'ice cream' to *my_foods*, it will also appear in *friend_foods*. Likewise 'rice' will appear in both lists, even though it appears to be added only to *friend_foods*. The output shows that both lists are the same now, which is not what we wanted.

---

## Tuples

Lists work well for storing collections of items that can change throughout the life of a program. The ability to modify lists is particularly important when you’re working with a list of users on a website or a list of characters in a game. However, sometimes you’ll want to create a list of items that cannot change. Tuples allow you to do just that. Python refers to values that cannot change as *immutable*, and an *immutable list* is called a *tuple*.

### Defining a Tuple

A *tuple* looks just like a *list* except you use parentheses instead of square brackets. Once you define a *tuple*, you can access individual elements by using each item’s index, just as you would for a *list*.

For example, if we have a rectangle that should always be a certain size, we can ensure that its size doesn’t change by putting the dimensions into a *tuple*:

```Python
rectangle_dimensions = (100, 50)
print(f"Length = {rectangle_dimensions[0]}")
print(f"Width = {rectangle_dimensions[1]}")
```

```CMD
Length = 100
Width = 50
```

Let’s see what happens if we try to change one of the items in the *tuple*:

```Python
rectangle_dimensions = (100, 50)
rectangle_dimensions[0] = 200
```

```CMD
TypeError: 'tuple' object does not support item assignment
```

The code tries to change the value of the first dimension, but Python returns a type error. Basically, because we’re trying to alter a *tuple*, which can’t be done to that type of object, Python tells us we can’t assign a new value to an item in a *tuple*.

This is beneficial because we want Python to raise an error when a line of code tries to change the dimensions of the rectangle.

*Tuples are technically defined by the presence of a comma; the parentheses make them look neater and more readable. If you want to define a tuple with one element, you need to include a trailing comma.* `my_tuple = (1,)`

*It doesn’t often make sense to build a tuple with one element, but this can happen when tuples are generated automatically.*

### Looping Through All Values in a Tuple

You can loop over all the values in a *tuple* using a for loop, just as you did with a *list*.

```Python
rectangle_dimensions = (100, 50)

for dimension in rectangle_dimensions:
    print(dimension)
```

```CMD
100
50
```

### Writing over a Tuple

Although you can’t modify a *tuple*, you can assign a new value to a variable that represents a *tuple*. So if we wanted to change our dimensions, we could redefine the entire *tuple*.

```Python
rectangle_dimensions = (200, 50)

print("Original dimensions:")
for dimension in rectangle_dimensions:
    print(dimension)

rectangle_dimensions = (400, 100)

print("\nModified dimensions:")
for dimension in rectangle_dimensions:
    print(dimension)
```

```CMD
Original dimensions:
200
50

Modified dimensions:
400
100
```

we associate a new tuple with the variable *rectangle_dimensions*. We then print the new dimensions. Python doesn’t raise any errors this time, because reassigning a variable is valid.

When compared with lists, tuples are simple data structures. Use them when you want to store a set of values that should not be changed throughout the life of a program.