Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
List type is another sequence type defined by the list class of python. List allows you add, delete or process elements in very simple ways. List is very similar to arrays.
You can create list using the following syntax.
here each elements in the list is separated by comma and enclosed by a pair of square brackets ([]
). Elements in the list can be of same type or different type. For e.g:
Other ways of creating list.
You can use index operator ([]
) to access individual elements in the list. List index starts from 0
.
Slice operator ([start:end]
) allows to fetch sublist from the list. It works similar to string.
Similar to string start
index is optional, if omitted it will be 0
.
The end
index is also optional, if omitted it will be set to the last index of the list.
note:
If start >= end
, list[start : end]
will return an empty list. If end specifies a position which is beyond the end
of the list, Python will use the length of the list for end
instead.
The +
operator joins the two list.
The *
operator replicates the elements in the list.
The in
operator is used to determine whether the elements exists in the list. On success it returns True
on failure it returns False
.
Similarly not in
is the opposite of in
operator.
As already discussed list is a sequence and also iterable. Means you can use for loop to loop through all the elements of the list.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
>>> list1 \= [2, 3, 4, 1, 32, 4] >>> list1.append(19) >>> list1 [2, 3, 4, 1, 32, 4, 19] >>> list1.count(4) # Return the count for number 4 2 >>> list2 \= [99, 54] >>> list1.extend(list2) >>> list1 [2, 3, 4, 1, 32, 4, 19, 99, 54] >>> list1.index(4) # Return the index of number 4 2 >>> list1.insert(1, 25) # Insert 25 at position index 1 >>> list1 [2, 25, 3, 4, 1, 32, 4, 19, 99, 54] >>> >>> list1 \= [2, 25, 3, 4, 1, 32, 4, 19, 99, 54] >>> list1.pop(2) 3 >>> list1 [2, 25, 4, 1, 32, 4, 19, 99, 54] >>> list1.pop() 54 >>> list1 [2, 25, 4, 1, 32, 4, 19, 99] >>> list1.remove(32) # Remove number 32 >>> list1 [2, 25, 4, 1, 4, 19, 99] >>> list1.reverse() # Reverse the list >>> list1 [99, 19, 4, 1, 4, 25, 2] >>> list1.sort() # Sort the list >>> list1 [1, 2, 4, 4, 19, 25, 99] >>>
List comprehension provides a concise way to create list. It consists of square brackets containing expression followed by for clause then zero or more for or if clauses.
Python is a flexible language, and there's typically several ways to perform the same, menial task. Choosing an approach can depend on the time or space complexity, or simply on your personal preference.
Python's data structures are quite handy and intuitive, and their built-in functionalities are easy to work with. In this article, we'll be looking at how to reverse a list in Python. A Python List is a heterogenous (can contain differing types) array-like structure that stores references to objects in memory.
When manipulating a list, we can either create a new, changed list, or change the original list in-place. We'll see the differences in these as we proceed through the article.
Python has a powerful built-in library of methods when it comes to manipulating data in data structures. For the purposes of reversing a list, we can utilize the built-in reverse()
method.
Note: The reverse()
method reverses the list in-place. Reversing a list in-place means that the original list is changed, instead of creating a new, reversed list.
Due to this, we can't assign the resulting object to a new variable, and if you want to keep the original list in memory, you'll have to copy it before reversing:
There's no return value - the list is reversed in-place. However, we can copy()
it before reversing:
This results in
The slice notation allows us to slice and reproduce parts of various collections or collection-based objects in Python, such as Lists, Strings, Tuples and NumPy Arrays.
When you slice a list, a portion is returned from that list, and every step
th element is included:
This results in:
By omitting the start
and end
arguments, you can include the entire collection. And by setting the step
to a negative number, you iterate through the collection in reverse. Naturally, if you pair these together:
This results in:
The Slice Notation doesn't affect the original list at all, so the original_list
stays the same even after the operation.
The slice()
method accepts the very same parameters - start
, end
and step
, and performs much the same operation as the Slice Notation. Though, instead of omitting the start
and end
arguments, you can pass in None
.
Its return type is a Slice
object, which can be then be used to slice a collection according to its contents. It's not called on the collection you're slicing - you're passing in the Slice
object after creation, allowing you to create a single reusable and callable object for many different collections.
It's internally transpiled into Slice Notation, so the end result is the same:
This results in:
Depending on whether we want to keep the original list intact or not, we can pop()
elements from the original list and add them to a new one, or we can just append them in reverse order.
pop()
removes the last element from a collection and returns it. We can combine the append()
method with this to directly append the removed element to a new list, effectively resulting in a reversed list:
Alternatively, we can iterate through the list backwards, until the -1
th index (non-inclusive) and add each element we see along that list. The range()
method accepts 3 arguments - start
, end
and step
, which can again be used in much the same way as before:
Since iterating with a negative step and then accessing each element in the original list is a bit verbose, the reversed()
method was added, which makes it much easier to manually implement the reversal logic, in case you want to add your own twist on it.
The reversed()
method returns an iterator, iterating over the collection in a reversed order - and we can easily add these elements into a new list:
Depending on whether you need a new reversed list, an in-place reversed list, as well as whether you want the logic to be taken care of for you, or if you'd like to have the flexibility of adding additional operations or twists during the reversal - there are several ways to reverse a list in Python.
In this tutorial, we've gone over these scenarios, highlighting the difference between each.
This chapter describes some things you’ve learned about already in more detail, and adds some new things as well.
The list data type has some more methods. Here are all of the methods of list objects:list.append
(x)
Add an item to the end of the list. Equivalent to a[len(a):] = [x]
.list.extend
(iterable)
Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable
.list.insert
(i, x)
Insert an item at a given position. The first argument is the index of the element before which to insert, so a.insert(0, x)
inserts at the front of the list, and a.insert(len(a), x)
is equivalent to a.append(x)
.list.remove
(x)
Remove the first item from the list whose value is equal to x. It raises a ValueError
if there is no such item.list.pop
([i])
Remove the item at the given position in the list, and return it. If no index is specified, a.pop()
removes and returns the last item in the list. (The square brackets around the i in the method signature denote that the parameter is optional, not that you should type square brackets at that position. You will see this notation frequently in the Python Library Reference.)list.clear
()
Remove all items from the list. Equivalent to del a[:]
.list.index
(x[, start[, end]])
Return zero-based index in the list of the first item whose value is equal to x. Raises a ValueError
if there is no such item.
The optional arguments start and end are interpreted as in the slice notation and are used to limit the search to a particular subsequence of the list. The returned index is computed relative to the beginning of the full sequence rather than the start argument.list.count
(x)
Return the number of times x appears in the list.list.sort
(*, key=None, reverse=False)
Sort the items of the list in place (the arguments can be used for sort customization, see sorted()
for their explanation).list.reverse
()
Reverse the elements of the list in place.list.copy
()
Return a shallow copy of the list. Equivalent to a[:]
.
An example that uses most of the list methods:>>>
You might have noticed that methods like insert
, remove
or sort
that only modify the list have no return value printed – they return the default None
. 1 This is a design principle for all mutable data structures in Python.
Another thing you might notice is that not all data can be sorted or compared. For instance, [None, 'hello', 10]
doesn’t sort because integers can’t be compared to strings and None can’t be compared to other types. Also, there are some types that don’t have a defined ordering relation. For example, 3+4j < 5+7j
isn’t a valid comparison.
The list methods make it very easy to use a list as a stack, where the last element added is the first element retrieved (“last-in, first-out”). To add an item to the top of the stack, use append()
. To retrieve an item from the top of the stack, use pop()
without an explicit index. For example:>>>
It is also possible to use a list as a queue, where the first element added is the first element retrieved (“first-in, first-out”); however, lists are not efficient for this purpose. While appends and pops from the end of list are fast, doing inserts or pops from the beginning of a list is slow (because all of the other elements have to be shifted by one).
To implement a queue, use collections.deque
which was designed to have fast appends and pops from both ends. For example:>>>
List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.
For example, assume we want to create a list of squares, like:>>>
Note that this creates (or overwrites) a variable named x
that still exists after the loop completes. We can calculate the list of squares without any side effects using:
or, equivalently:
which is more concise and readable.
A list comprehension consists of brackets containing an expression followed by a for
clause, then zero or more for
or if
clauses. The result will be a new list resulting from evaluating the expression in the context of the for
and if
clauses which follow it. For example, this listcomp combines the elements of two lists if they are not equal:>>>
and it’s equivalent to:>>>
Note how the order of the for
and if
statements is the same in both these snippets.
If the expression is a tuple (e.g. the (x, y)
in the previous example), it must be parenthesized.>>>
List comprehensions can contain complex expressions and nested functions:>>>
The initial expression in a list comprehension can be any arbitrary expression, including another list comprehension.
Consider the following example of a 3x4 matrix implemented as a list of 3 lists of length 4:>>>
The following list comprehension will transpose rows and columns:>>>
As we saw in the previous section, the nested listcomp is evaluated in the context of the for
that follows it, so this example is equivalent to:>>>
which, in turn, is the same as:>>>
In the real world, you should prefer built-in functions to complex flow statements. The zip()
function would do a great job for this use case:>>>
See Unpacking Argument Lists for details on the asterisk in this line.
del
statementThere is a way to remove an item from a list given its index instead of its value: the del
statement. This differs from the pop()
method which returns a value. The del
statement can also be used to remove slices from a list or clear the entire list (which we did earlier by assignment of an empty list to the slice). For example:>>>
del
can also be used to delete entire variables:>>>
Referencing the name a
hereafter is an error (at least until another value is assigned to it). We’ll find other uses for del
later.
We saw that lists and strings have many common properties, such as indexing and slicing operations. They are two examples of sequence data types (see Sequence Types — list, tuple, range). Since Python is an evolving language, other sequence data types may be added. There is also another standard sequence data type: the tuple.
A tuple consists of a number of values separated by commas, for instance:>>>
As you see, on output tuples are always enclosed in parentheses, so that nested tuples are interpreted correctly; they may be input with or without surrounding parentheses, although often parentheses are necessary anyway (if the tuple is part of a larger expression). It is not possible to assign to the individual items of a tuple, however it is possible to create tuples which contain mutable objects, such as lists.
Though tuples may seem similar to lists, they are often used in different situations and for different purposes. Tuples are immutable, and usually contain a heterogeneous sequence of elements that are accessed via unpacking (see later in this section) or indexing (or even by attribute in the case of namedtuples
). Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.
A special problem is the construction of tuples containing 0 or 1 items: the syntax has some extra quirks to accommodate these. Empty tuples are constructed by an empty pair of parentheses; a tuple with one item is constructed by following a value with a comma (it is not sufficient to enclose a single value in parentheses). Ugly, but effective. For example:>>>
The statement t = 12345, 54321, 'hello!'
is an example of tuple packing: the values 12345
, 54321
and 'hello!'
are packed together in a tuple. The reverse operation is also possible:>>>
This is called, appropriately enough, sequence unpacking and works for any sequence on the right-hand side. Sequence unpacking requires that there are as many variables on the left side of the equals sign as there are elements in the sequence. Note that multiple assignment is really just a combination of tuple packing and sequence unpacking.
Python also includes a data type for sets. A set is an unordered collection with no duplicate elements. Basic uses include membership testing and eliminating duplicate entries. Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.
Curly braces or the set()
function can be used to create sets. Note: to create an empty set you have to use set()
, not {}
; the latter creates an empty dictionary, a data structure that we discuss in the next section.
Here is a brief demonstration:>>>
Similarly to list comprehensions, set comprehensions are also supported:>>>
Another useful data type built into Python is the dictionary (see Mapping Types — dict). Dictionaries are sometimes found in other languages as “associative memories” or “associative arrays”. Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like append()
and extend()
.
It is best to think of a dictionary as a set of key: value pairs, with the requirement that the keys are unique (within one dictionary). A pair of braces creates an empty dictionary: {}
. Placing a comma-separated list of key:value pairs within the braces adds initial key:value pairs to the dictionary; this is also the way dictionaries are written on output.
The main operations on a dictionary are storing a value with some key and extracting the value given the key. It is also possible to delete a key:value pair with del
. If you store using a key that is already in use, the old value associated with that key is forgotten. It is an error to extract a value using a non-existent key.
Performing list(d)
on a dictionary returns a list of all the keys used in the dictionary, in insertion order (if you want it sorted, just use sorted(d)
instead). To check whether a single key is in the dictionary, use the in
keyword.
Here is a small example using a dictionary:>>>
The dict()
constructor builds dictionaries directly from sequences of key-value pairs:>>>
In addition, dict comprehensions can be used to create dictionaries from arbitrary key and value expressions:>>>
When the keys are simple strings, it is sometimes easier to specify pairs using keyword arguments:>>>
When looping through dictionaries, the key and corresponding value can be retrieved at the same time using the items()
method.>>>
When looping through a sequence, the position index and corresponding value can be retrieved at the same time using the enumerate()
function.>>>
To loop over two or more sequences at the same time, the entries can be paired with the zip()
function.>>>
To loop over a sequence in reverse, first specify the sequence in a forward direction and then call the reversed()
function.>>>
To loop over a sequence in sorted order, use the sorted()
function which returns a new sorted list while leaving the source unaltered.>>>
Using set()
on a sequence eliminates duplicate elements. The use of sorted()
in combination with set()
over a sequence is an idiomatic way to loop over unique elements of the sequence in sorted order.>>>
It is sometimes tempting to change a list while you are looping over it; however, it is often simpler and safer to create a new list instead.>>>
The conditions used in while
and if
statements can contain any operators, not just comparisons.
The comparison operators in
and not in
check whether a value occurs (does not occur) in a sequence. The operators is
and is not
compare whether two objects are really the same object. All comparison operators have the same priority, which is lower than that of all numerical operators.
Comparisons can be chained. For example, a < b == c
tests whether a
is less than b
and moreover b
equals c
.
Comparisons may be combined using the Boolean operators and
and or
, and the outcome of a comparison (or of any other Boolean expression) may be negated with not
. These have lower priorities than comparison operators; between them, not
has the highest priority and or
the lowest, so that A and not B or C
is equivalent to (A and (not B)) or C
. As always, parentheses can be used to express the desired composition.
The Boolean operators and
and or
are so-called short-circuit operators: their arguments are evaluated from left to right, and evaluation stops as soon as the outcome is determined. For example, if A
and C
are true but B
is false, A and B and C
does not evaluate the expression C
. When used as a general value and not as a Boolean, the return value of a short-circuit operator is the last evaluated argument.
It is possible to assign the result of a comparison or other Boolean expression to a variable. For example,>>>
Note that in Python, unlike C, assignment inside expressions must be done explicitly with the walrus operator :=
. This avoids a common class of problems encountered in C programs: typing =
in an expression when ==
was intended.
Sequence objects typically may be compared to other objects with the same sequence type. The comparison uses lexicographical ordering: first the first two items are compared, and if they differ this determines the outcome of the comparison; if they are equal, the next two items are compared, and so on, until either sequence is exhausted. If two items to be compared are themselves sequences of the same type, the lexicographical comparison is carried out recursively. If all items of two sequences compare equal, the sequences are considered equal. If one sequence is an initial sub-sequence of the other, the shorter sequence is the smaller (lesser) one. Lexicographical ordering for strings uses the Unicode code point number to order individual characters. Some examples of comparisons between sequences of the same type:
Note that comparing objects of different types with <
or >
is legal provided that the objects have appropriate comparison methods. For example, mixed numeric types are compared according to their numeric value, so 0 equals 0.0, etc. Otherwise, rather than providing an arbitrary ordering, the interpreter will raise a TypeError
exception.
In Python, lists are represented by square brackets. Therefore, we create a list as follows.
The above list, colors
is stored in memory as shown below.
We can also create a list that contains multiple data types, like strings, integers, and floats.
Python lists follow a zero indexing structure, meaning the list index starts from 0. Nested lists are accessed using nested indexing.
Python has a very handy negative indexing feature as well, which starts from the end of the list:
We can reverse and slice lists using list indices, as follows
For more information regarding list slicing, refer to this link.
list.index()
list.index()
returns the index of a specified element in the list. The syntax is: list.index(element, start, end)
list.append()
The list.append()
method adds an item at the end of a list.
list.extend()
list.extend()
extends the list by appending items.
list.insert()
list.insert()
inserts an element into the mentioned index.
list.remove()
list.remove()
removes the first element that matches from the specified list.
list.count(x)
list.count()
returns the number of times that ‘x’ appears in the list.
list.pop()
The list.pop()
method removes and returns the element specified in the parameter. If the parameter is not specified, it removes and returns the last element in the list.
list.reverse()
The list.reverse()
method reverses the list, and updates it. It has no return value.
list.sort()
The list.sort()
method sorts the elements of the given list using the syntax: list.sort(key= , reverse= )
list.copy()
The list.copy()
method copies the list into another list.
list.clear()
The list.clear()
method empties the given list.
List Comprehensions are advanced features in Python that enable you to create a new list from an existing list, and it consists of expressions within a for statement inside square brackets.
For example:
Lists are one of the most commonly used and most powerful data structures in Python. If one manages to master lists, he/she will perform very well in programming interviews. Once you’re done reading and using the list methods, check out the below links and start solving programs based on lists.
Last chapter we introduced Python’s built-in types int
, float
, and str
, and we stumbled upon tuple
.
Integers and floats are numeric types, which means they hold numbers. We can use the numeric operators we saw last chapter with them to form numeric expressions. The Python interpreter can then evaluate these expressions to produce numeric values, making Python a very powerful calculator.
Strings, lists, and tuples are all sequence types, so called because they behave like a sequence - an ordered collection of objects.
Squence types are qualitatively different from numeric types because they are compound data types - meaning they are made up of smaller pieces. In the case of strings, they’re made up of smaller strings, each containing one character. There is also the empty string, containing no characters at all.
In the case of lists or tuples, they are made up of elements, which are values of any Python datatype, including other lists and tuples.
Lists are enclosed in square brackets ([
and ]
) and tuples in parentheses ((
and )
).
A list containing no elements is called an empty list, and a tuple with no elements is an empty tuple.
The first example is a list of five integers, and the next is a list of three strings. The third is a tuple containing four integers, followed by a tuple containing four strings. The last is a list containing three tuples, each of which contains a pair of strings.
Depending on what we are doing, we may want to treat a compound data type as a single thing, or we may want to access its parts. This ambiguity is useful.
Note
It is possible to drop the parentheses when specifiying a tuple, and only use a comma seperated list of values:
Also, it is required to include a comma when specifying a tuple with only one element:
Except for the case of the empty tuple, it is really the commas, not the parentheses, that tell Python it is a tuple.
The sequence types share a common set of operations.
The indexing operator ([
]
) selects a single element from a sequence. The expression inside brackets is called the index, and must be an integer value. The index indicates which element to select, hence its name.
The expression fruit[1]
selects the character with index 1
from fruit
, and creates a new string containing just this one character, which you may be surprised to see is 'a'
.
You probably expected to see 'b'
, but computer scientists typically start counting from zero, not one. Think of the index as the numbers on a ruler measuring how many elements you have moved into the sequence from the beginning. Both rulers and indices start at 0
.
Last chapter you saw the len
function used to get the number of characters in a string:
With lists and tuples, len
returns the number of elements in the sequence:
It is common in computer programming to need to access elements at the end of a sequence. Now that you have seen the len
function, you might be tempted to try something like this:
That won’t work. It causes the runtime error IndexError: list index out of range
. The reason is that len(seq)
returns the number of elements in the list, 16, but there is no element at index position 16 in seq
.
Since we started counting at zero, the sixteen indices are numbered 0 to 15. To get the last element, we have to subtract 1 from the length:
This is such a common in pattern that Python provides a short hand notation for it, negative indexing, which counts backward from the end of the sequence.
The expression seq[-1]
yields the last element, seq[-2]
yields the second to last, and so on.
for
loopA lot of computations involve processing a sequence one element at a time. The most common pattern is to start at the beginning, select each element in turn, do something to it, and continue until the end. This pattern of processing is called a traversal.
Python’s for
loop makes traversal easy to express:
Note
We will discuss looping in greater detail in the next chapter. For now just note that the colon (:) at the end of the first line and the indentation on the second line are both required for this statement to be syntactically correct.
enumerate
As the standard for
loop traverses a sequence, it assigns each value in the sequence to the loop variable in the order it occurs in the sequence. Sometimes it is helpful to have both the value and the index of each element. The enumerate
function gives us this:
A subsequence of a sequence is called a slice and the operation that extracts a subsequence is called slicing. Like with indexing, we use square brackets ([
]
) as the slice operator, but instead of one integer value inside we have two, seperated by a colon (:
):
If you omit the first index (before the colon), the slice starts at the beginning of the string. If you omit the second index, the slice goes to the end of the string. Thus:
What do you think s[:]
means? What about classmates[4:]
?
Negative indexes are also allowed, so
Tip
Developing a firm understanding of how slicing works is important. Keep creating your own “experiments” with sequences and slices until you can consistently predict the result of a slicing operation before you run it.
When you slice a sequence, the resulting subsequence always has the same type as the sequence from which it was derived. This is not generally true with indexing, except in the case of strings.
While the elements of a list (or tuple) can be of any type, no matter how you slice it, a slice of a list is a list.
in
operatorThe in
operator returns whether a given element is contained in a list or tuple:
in
works somewhat differently with strings. It evaluates to True
if one string is a substring of another:
Note that a string is a substring of itself, and the empty string is a substring of any other string. (Also note that computer programmers like to think about these edge cases quite carefully!)
Strings, lists, and tuples are objects, which means that they not only hold values, but have built-in behaviors called methods, that act on the values in the object.
Let’s look at some string methods in action to see how this works.
Now let’s learn to describe what we just saw. Each string in the above examples is followed by a dot operator, a method name, and a parameter list, which may be empty.
In the first example, the string 'apple'
is followed by the dot operator and then the upper()
method, which has an empty parameter list. We say that the “upper()
method is invoked on the string, 'apple'
. Invoking the method causes an action to take place using the value on which the method is invoked. The action produces a result, in this case the string value 'Apple'
. We say that the upper()
method returns the string 'Apple'
when it is invoked on (or called on) the string 'apple'
.
In the fourth example, the method isdigit()
(again with an empty parameter list) is invoked on the string '42'
. Since each of the characters in the string represents a digit, the isdigit()
method returns the boolean value True
. Invoking isdigit()
on 'four'
produces False
.
The strip()
removes leading and trailing whitespace.
dir()
function and docstringsThe previous section introduced several of the methods of string objects. To find all the methods that strings have, we can use Python’s built-in dir
function:
We will postpone talking about the ones that begin with double underscores (__
) until later. You can find out more about each of these methods by printing out their docstrings. To find out what the replace
method does, for example, we do this:
Using this information, we can try using the replace method to varify that we know how it works.
The first example replaces all occurances of 'i'
with 'X'
. The second replaces the single character 'p'
with the two characters 'MO'
. The third example replaces the first two occurances of 'i''
with the empty string.
count
and index
methodsThere are two methods that are common to all three sequence types: count
and index
. Let’s look at their docstrings to see what they do.
We will explore these functions in the exercises.
Unlike strings and tuples, which are immutable objects, lists are mutable, which means we can change their elements. Using the bracket operator on the left side of an assignment, we can update one of the elements:
The bracket operator applied to a list can appear anywhere in an expression. When it appears on the left side of an assignment, it changes one of the elements in the list, so the first element of fruit
has been changed from 'banana'
to 'pear'
, and the last from 'quince'
to 'orange'
. An assignment to an element of a list is called item assignment. Item assignment does not work for strings:
but it does for lists:
With the slice operator we can update several elements at once:
We can also remove elements from a list by assigning the empty list to them:
And we can add elements to a list by squeezing them into an empty slice at the desired location:
Using slices to delete list elements can be awkward, and therefore error-prone. Python provides an alternative that is more readable.
del
removes an element from a list:
As you might expect, del
handles negative indices and causes a runtime error if the index is out of range.
You can use a slice as an index for del
:
As usual, slices select all the elements up to, but not including, the second index.
In addition to count
and index
, lists have several useful methods. Since lists are mutable, these methods modify the list on which they are invoked, rather than returning a new list.
The sort
method is particularly useful, since it makes it easy to use Python to sort data that you have put in a list.
If we execute these assignment statements,
we know that the names a
and b
will refer to a list with the numbers 1
, 2
, and 3
. But we don’t know yet whether they point to the same list.
In one case, a
and b
refer to two different things that have the same value. In the second case, they refer to the same object.
We can test whether two names have the same value using ==
:
We can test whether two names refer to the same object using the is operator:
This tells us that both a
and b
do not refer to the same object, and that it is the first of the two state diagrams that describes the relationship.
Since variables refer to objects, if we assign one variable to another, both variables refer to the same object:
In this case, it is the second of the two state diagrams that describes the relationship between the variables.
Because the same list has two different names, a
and b
, we say that it is aliased. Since lists are mutable, changes made with one alias affect the other:
Although this behavior can be useful, it is sometimes unexpected or undesirable. In general, it is safer to avoid aliasing when you are working with mutable objects. Of course, for immutable objects, there’s no problem, since they can’t be changed after they are created.
If we want to modify a list and also keep a copy of the original, we need to be able to make a copy of the list itself, not just the reference. This process is sometimes called cloning, to avoid the ambiguity of the word copy.
The easiest way to clone a list is to use the slice operator:
Taking any slice of a
creates a new list. In this case the slice happens to consist of the whole list.
Now we are free to make changes to b
without worrying about a
:
A nested list is a list that appears as an element in another list. In this list, the element with index 3 is a nested list:
If we print nested[3]
, we get [10, 20]
. To extract an element from the nested list, we can proceed in two steps:
Or we can combine them:
Bracket operators evaluate from left to right, so this expression gets the three-eth element of nested
and extracts the one-eth element from it.
Python has several tools which combine lists of strings into strings and separate strings into lists of strings.
The list
command takes a sequence type as an argument and creates a list out of its elements. When applied to a string, you get a list of characters.
The split
method invoked on a string and separates the string into a list of strings, breaking it apart whenever a substring called the delimiter occurs. The default delimiter is whitespace, which includes spaces, tabs, and newlines.
Here we have 'o'
as the delimiter.
Notice that the delimiter doesn’t appear in the list.
The join
method does approximately the oposite of the split
method. It takes a list of strings as an argument and returns a string of all the list elements joined together.
The string value on which the join
method is invoked acts as a separator that gets placed between each element in the list in the returned string.
The separator can also be the empty string.
Once in a while, it is useful to swap the values of two variables. With conventional assignment statements, we have to use a temporary variable. For example, to swap a
and b
:
If we have to do this often, this approach becomes cumbersome. Python provides a form of tuple assignment that solves this problem neatly:
The left side is a tuple of variables; the right side is a tuple of values. Each value is assigned to its respective variable. All the expressions on the right side are evaluated before any of the assignments. This feature makes tuple assignment quite versatile.
Naturally, the number of variables on the left and the number of values on the right have to be the same:
We will now look at a new type of value - boolean values - named after the British mathematician, George Boole. He created the mathematics we call Boolean algebra, which is the basis of all modern computer arithmetic.
Note
It is a computer’s ability to alter its flow of execution depending on whether a boolean value is true or false that makes a general purpose computer more than just a calculator.
There are only two boolean values, True
and False
.
Capitalization is important, since true
and false
are not boolean values in Python.:
A boolean expression is an expression that evaluates to a boolean value.
The operator ==
compares two values and produces a boolean value:
In the first statement, the two operands are equal, so the expression evaluates to True
; in the second statement, 5 is not equal to 6, so we get False
.
The ==
operator is one of six common comparison operators; the others are:
Although these operations are probably familiar to you, the Python symbols are different from the mathematical symbols. A common error is to use a single equal sign (=
) instead of a double equal sign (==
). Remember that =
is an assignment operator and ==
is a comparison operator. Also, there is no such thing as =<
or =>
.
There are three logical operators: and
, or
, and not
. The semantics (meaning) of these operators is similar to their meaning in English. For example, x > 0 and x < 10
is true only if x
is greater than 0 and at the same time, x is less than 10.
n % 2 == 0 or n % 3 == 0
is true if either of the conditions is true, that is, if the number is divisible by 2 or divisible by 3.
Finally, the not
operator negates a boolean expression, so not (x > y)
is true if (x > y)
is false, that is, if x
is less than or equal to y
.
Boolean expressions in Python use short-circuit evaluation, which means only the first argument of an and
or or
expression is evaluated when its value is suffient to determine the value of the entire expression.
This can be quite useful in preventing runtime errors. Imagine you want check if the fifth number in a tuple of integers named numbers
is even.
The following expression will work:
unless of course there are not 5 elements in numbers
, in which case you will get:
Short-circuit evaluation makes it possible to avoid this problem.
Since the left hand side of this and
expression is false, Python does not need to evaluate the right hand side to determine that the whole expression is false. Since it uses short-circuit evaluation, it does not, and the runtime error is avoided.
All Python values have a “truthiness” or “falsiness” which means they can be used in places requiring a boolean. For the numeric and sequence types we have seen thus far, truthiness is defined as follows:numberic types
Values equal to 0 are false, all others are true.sequence types
Empty sequences are false, non-empty sequences are true.
Combining this notion of truthiness with an understanding of short-circuit evaluation makes it possible to understand what Python is doing in the following expressions:
aliases
Multiple variables that contain references to the same object.boolean value
There are exactly two boolean values: True
and False
. Boolean values result when a boolean expression is evaluated by the Python interepreter. They have type bool
.boolean expression
An expression that is either true or false.clone
To create a new object that has the same value as an existing object. Copying a reference to an object creates an alias but doesn’t clone the object.comparison operator
One of the operators that compares two values: ==
, !=
, >
, <
, >=
, and <=
.compound data type
A data type in which the values are made up of components, or elements, that are themselves values.element
One of the parts that make up a sequence type (string, list, or tuple). Elements have a value and an index. The value is accessed by using the index operator ([*index*]
) on the sequence.immutable data type
A data type which cannot be modified. Assignments to elements or slices of immutable types cause a runtime error.index
A variable or value used to select a member of an ordered collection, such as a character from a string, or an element from a list or tuple.logical operator
One of the operators that combines boolean expressions: and
, or
, and not
.mutable data type
A data type which can be modified. All mutable types are compound types. Lists and dictionaries are mutable data types; strings and tuples are not.nested list
A list that is an element of another list.slice
A part of a string (substring) specified by a range of indices. More generally, a subsequence of any sequence type in Python can be created using the slice operator (sequence[start:stop]
).step size
The interval between successive elements of a linear sequence. The third (and optional argument) to the range
function is called the step size. If not specified, it defaults to 1.traverse
To iterate through the elements of a collection, performing a similar operation on each.tuple
A data type that contains a sequence of elements of any type, like a list, but is immutable. Tuples can be used wherever an immutable type is required, such as a key in a dictionary (see next chapter).tuple assignment
An assignment to all of the elements in a tuple using a single assignment statement. Tuple assignment occurs in parallel rather than in sequence, making it useful for swapping values.
A list is a value that contains multiple values in an ordered sequence. The term list value refers to the list itself (which is a value that can be stored in a variable or passed to a function like any other value), not the values inside the list value. A list value looks like this: ['cat', 'bat', 'rat', 'elephant']. Just as string values are typed with quote characters to mark where the string begins and ends, a list begins with an opening square bracket and ends with a closing square bracket, []. Values inside the list are also called items. Items are separated with commas (that is, they are comma-delimited). For example, enter the following into the interactive shell:
The spam variable ➊ is still assigned only one value: the list value. But the list value itself contains other values. The value [] is an empty list that contains no values, similar to '', the empty string.
Getting Individual Values in a List with Indexes
Say you have the list ['cat', 'bat', 'rat', 'elephant'] stored in a variable named spam. The Python code spam[0] would evaluate to 'cat', and spam[1] would evaluate to 'bat', and so on. The integer inside the square brackets that follows the list is called an index. The first value in the list is at index 0, the second value is at index 1, the third value is at index 2, and so on. Figure 4-1 shows a list value assigned to spam, along with what the index expressions would evaluate to. Note that because the first index is 0, the last index is one less than the size of the list; a list of four items has 3 as its last index.
Figure 4-1: A list value stored in the variable spam, showing which value each index refers to
For example, enter the following expressions into the interactive shell. Start by assigning a list to the variable spam.
Notice that the expression 'Hello, ' + spam[0] ➊ evaluates to 'Hello, ' + 'cat' because spam[0] evaluates to the string 'cat'. This expression in turn evaluates to the string value 'Hello, cat' ➋.
Python will give you an IndexError error message if you use an index that exceeds the number of values in your list value.
Indexes can be only integer values, not floats. The following example will cause a TypeError error:
>>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam[1] 'bat' >>> spam[1.0] Traceback (most recent call last): File "<pyshell#13>", line 1, in <module> spam[1.0] TypeError: list indices must be integers or slices, not float >>> spam[int(1.0)] 'bat'
Lists can also contain other list values. The values in these lists of lists can be accessed using multiple indexes, like so:
The first index dictates which list value to use, and the second indicates the value within the list value. For example, spam[0][1] prints 'bat', the second value in the first list. If you only use one index, the program will print the full list value at that index.
Negative Indexes
While indexes start at 0 and go up, you can also use negative integers for the index. The integer value -1 refers to the last index in a list, the value -2 refers to the second-to-last index in a list, and so on. Enter the following into the interactive shell:
Getting a List from Another List with Slices
Just as an index can get a single value from a list, a slice can get several values from a list, in the form of a new list. A slice is typed between square brackets, like an index, but it has two integers separated by a colon. Notice the difference between indexes and slices.
spam[2] is a list with an index (one integer).
spam[1:4] is a list with a slice (two integers).
In a slice, the first integer is the index where the slice starts. The second integer is the index where the slice ends. A slice goes up to, but will not include, the value at the second index. A slice evaluates to a new list value. Enter the following into the interactive shell:
As a shortcut, you can leave out one or both of the indexes on either side of the colon in the slice. Leaving out the first index is the same as using 0, or the beginning of the list. Leaving out the second index is the same as using the length of the list, which will slice to the end of the list. Enter the following into the interactive shell:
Getting a List’s Length with the len() Function
The len() function will return the number of values that are in a list value passed to it, just like it can count the number of characters in a string value. Enter the following into the interactive shell:
>>> spam = ['cat', 'dog', 'moose'] >>> len(spam) 3
Changing Values in a List with Indexes
Normally, a variable name goes on the left side of an assignment statement, like spam = 42. However, you can also use an index of a list to change the value at that index. For example, spam[1] = 'aardvark' means “Assign the value at index 1 in the list spam to the string 'aardvark'.” Enter the following into the interactive shell:
List Concatenation and List Replication
Lists can be concatenated and replicated just like strings. The + operator combines two lists to create a new list value and the * operator can be used with a list and an integer value to replicate the list. Enter the following into the interactive shell:
Removing Values from Lists with del Statements
The del statement will delete values at an index in a list. All of the values in the list after the deleted value will be moved up one index. For example, enter the following into the interactive shell:
The del statement can also be used on a simple variable to delete it, as if it were an “unassignment” statement. If you try to use the variable after deleting it, you will get a NameError error because the variable no longer exists. In practice, you almost never need to delete simple variables. The del statement is mostly used to delete values from lists.
When you first begin writing programs, it’s tempting to create many individual variables to store a group of similar values. For example, if I wanted to store the names of my cats, I might be tempted to write code like this:
It turns out that this is a bad way to write code. (Also, I don’t actually own this many cats, I swear.) For one thing, if the number of cats changes, your program will never be able to store more cats than you have variables. These types of programs also have a lot of duplicate or nearly identical code in them. Consider how much duplicate code is in the following program, which you should enter into the file editor and save as allMyCats1.py:
Instead of using multiple, repetitive variables, you can use a single variable that contains a list value. For example, here’s a new and improved version of the allMyCats1.py program. This new version uses a single list and can store any number of cats that the user types in. In a new file editor window, enter the following source code and save it as allMyCats2.py:
When you run this program, the output will look something like this:
Enter the name of cat 1 (Or enter nothing to stop.): Zophie Enter the name of cat 2 (Or enter nothing to stop.): Pooka Enter the name of cat 3 (Or enter nothing to stop.): Simon Enter the name of cat 4 (Or enter nothing to stop.): Lady Macbeth Enter the name of cat 5 (Or enter nothing to stop.): Fat-tail Enter the name of cat 6 (Or enter nothing to stop.): Miss Cleo Enter the name of cat 7 (Or enter nothing to stop.): The cat names are: Zophie Pooka Simon Lady Macbeth Fat-tail Miss Cleo
You can view the execution of these programs at https://autbor.com/allmycats1/ and https://autbor.com/allmycats2/. The benefit of using a list is that your data is now in a structure, so your program is much more flexible in processing the data than it would be with several repetitive variables.
Using for Loops with Lists
In Chapter 2, you learned about using for loops to execute a block of code a certain number of times. Technically, a for loop repeats the code block once for each item in a list value. For example, if you ran this code:
for i in range(4): print(i)
the output of this program would be as follows:
0 1 2 3
This is because the return value from range(4) is a sequence value that Python considers similar to [0, 1, 2, 3]. (Sequences are described in “Sequence Data Types” on page 93.) The following program has the same output as the previous one:
for i in [0, 1, 2, 3]: print(i)
The previous for loop actually loops through its clause with the variable i set to a successive value in the [0, 1, 2, 3] list in each iteration.
A common Python technique is to use range(len(someList)) with a for loop to iterate over the indexes of a list. For example, enter the following into the interactive shell:
>>> supplies = ['pens', 'staplers', 'flamethrowers', 'binders'] >>> for i in range(len(supplies)): ... print('Index ' + str(i) + ' in supplies is: ' + supplies[i]) Index 0 in supplies is: pens Index 1 in supplies is: staplers Index 2 in supplies is: flamethrowers Index 3 in supplies is: binders
Using range(len(supplies)) in the previously shown for loop is handy because the code in the loop can access the index (as the variable i) and the value at that index (as supplies[i]). Best of all, range(len(supplies)) will iterate through all the indexes of supplies, no matter how many items it contains.
The in and not in Operators
You can determine whether a value is or isn’t in a list with the in and not in operators. Like other operators, in and not in are used in expressions and connect two values: a value to look for in a list and the list where it may be found. These expressions will evaluate to a Boolean value. Enter the following into the interactive shell:
For example, the following program lets the user type in a pet name and then checks to see whether the name is in a list of pets. Open a new file editor window, enter the following code, and save it as myPets.py:
myPets = ['Zophie', 'Pooka', 'Fat-tail'] print('Enter a pet name:') name = input() if name not in myPets: print('I do not have a pet named ' + name) else: print(name + ' is my pet.')
The output may look something like this:
Enter a pet name: Footfoot I do not have a pet named Footfoot
You can view the execution of this program at https://autbor.com/mypets/.
https://pythontutor.com/visualize.html#
The Multiple Assignment Trick
The multiple assignment trick (technically called tuple unpacking) is a shortcut that lets you assign multiple variables with the values in a list in one line of code. So instead of doing this:
>>> cat = ['fat', 'gray', 'loud'] >>> size = cat[0] >>> color = cat[1] >>> disposition = cat[2]
you could type this line of code:
>>> cat = ['fat', 'gray', 'loud'] >>> size, color, disposition = cat
The number of variables and the length of the list must be exactly equal, or Python will give you a ValueError:
>>> cat = ['fat', 'gray', 'loud'] >>> size, color, disposition, name = cat Traceback (most recent call last): File "<pyshell#84>", line 1, in <module> size, color, disposition, name = cat ValueError: not enough values to unpack (expected 4, got 3)
Using the enumerate() Function with Lists
Instead of using the range(len(someList)) technique with a for loop to obtain the integer index of the items in the list, you can call the enumerate() function instead. On each iteration of the loop, enumerate() will return two values: the index of the item in the list, and the item in the list itself. For example, this code is equivalent to the code in the “Using for Loops with Lists”
>>> supplies = ['pens', 'staplers', 'flamethrowers', 'binders'] >>> for index, item in enumerate(supplies): ... print('Index ' + str(index) + ' in supplies is: ' + item) Index 0 in supplies is: pens Index 1 in supplies is: staplers Index 2 in supplies is: flamethrowers Index 3 in supplies is: binders
The enumerate() function is useful if you need both the item and the item’s index in the loop’s block.
Using the random.choice() and random.shuffle() Functions with Lists
The random module has a couple functions that accept lists for arguments. The random.choice() function will return a randomly selected item from the list. Enter the following into the interactive shell:
>>> import random >>> pets = ['Dog', 'Cat', 'Moose'] >>> random.choice(pets) 'Dog' >>> random.choice(pets) 'Cat' >>> random.choice(pets) 'Cat'
You can consider random.choice(someList) to be a shorter form of someList[random.randint(0, len(someList) – 1].
The random.shuffle() function will reorder the items in a list. This function modifies the list in place, rather than returning a new list. Enter the following into the interactive shell:
>>> import random >>> people = ['Alice', 'Bob', 'Carol', 'David'] >>> random.shuffle(people) >>> people ['Carol', 'David', 'Alice', 'Bob'] >>> random.shuffle(people) >>> people ['Alice', 'David', 'Bob', 'Carol']
When assigning a value to a variable, you will frequently use the variable itself. For example, after assigning 42 to the variable spam, you would increase the value in spam by 1 with the following code:
>>> spam = 42 >>> spam = spam + 1 >>> spam 43
As a shortcut, you can use the augmented assignment operator += to do the same thing:
>>> spam = 42 >>> spam += 1 >>> spam 43
There are augmented assignment operators for the +, -, *, /, and % operators, described in Table 4-1.
Table 4-1: The Augmented Assignment Operators
The += operator can also do string and list concatenation, and the *= operator can do string and list replication. Enter the following into the interactive shell:
>>> spam = 'Hello,' >>> spam += ' world!' >>> spam 'Hello world!' >>> bacon = ['Zophie'] >>> bacon *= 3 >>> bacon ['Zophie', 'Zophie', 'Zophie']
A method is the same thing as a function, except it is “called on” a value. For example, if a list value were stored in spam, you would call the index() list method (which I’ll explain shortly) on that list like so: spam.index('hello'). The method part comes after the value, separated by a period.
Each data type has its own set of methods. The list data type, for example, has several useful methods for finding, adding, removing, and otherwise manipulating values in a list.
Finding a Value in a List with the index() Method
List values have an index() method that can be passed a value, and if that value exists in the list, the index of the value is returned. If the value isn’t in the list, then Python produces a ValueError error. Enter the following into the interactive shell:
>>> spam = ['hello', 'hi', 'howdy', 'heyas'] >>> spam.index('hello') 0 >>> spam.index('heyas') 3 >>> spam.index('howdy howdy howdy') Traceback (most recent call last): File "<pyshell#31>", line 1, in <module> spam.index('howdy howdy howdy') ValueError: 'howdy howdy howdy' is not in list
When there are duplicates of the value in the list, the index of its first appearance is returned. Enter the following into the interactive shell, and notice that index() returns 1, not 3:
>>> spam = ['Zophie', 'Pooka', 'Fat-tail', 'Pooka'] >>> spam.index('Pooka') 1
Adding Values to Lists with the append() and insert() Methods
To add new values to a list, use the append() and insert() methods. Enter the following into the interactive shell to call the append() method on a list value stored in the variable spam:
>>> spam = ['cat', 'dog', 'bat'] >>> spam.append('moose') >>> spam ['cat', 'dog', 'bat', 'moose']
The previous append() method call adds the argument to the end of the list. The insert() method can insert a value at any index in the list. The first argument to insert() is the index for the new value, and the second argument is the new value to be inserted. Enter the following into the interactive shell:
>>> spam = ['cat', 'dog', 'bat'] >>> spam.insert(1, 'chicken') >>> spam ['cat', 'chicken', 'dog', 'bat']
Notice that the code is spam.append('moose') and spam.insert(1, 'chicken'), not spam = spam.append('moose') and spam = spam.insert(1, 'chicken'). Neither append() nor insert() gives the new value of spam as its return value. (In fact, the return value of append() and insert() is None, so you definitely wouldn’t want to store this as the new variable value.) Rather, the list is modified in place. Modifying a list in place is covered in more detail later in “Mutable and Immutable Data Types” on page 94.
Methods belong to a single data type. The append() and insert() methods are list methods and can be called only on list values, not on other values such as strings or integers. Enter the following into the interactive shell, and note the AttributeError error messages that show up:
>>> eggs = 'hello' >>> eggs.append('world') Traceback (most recent call last): File "<pyshell#19>", line 1, in <module> eggs.append('world') AttributeError: 'str' object has no attribute 'append' >>> bacon = 42 >>> bacon.insert(1, 'world') Traceback (most recent call last): File "<pyshell#22>", line 1, in <module> bacon.insert(1, 'world') AttributeError: 'int' object has no attribute 'insert'
Removing Values from Lists with the remove() Method
The remove() method is passed the value to be removed from the list it is called on. Enter the following into the interactive shell:
>>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam.remove('bat') >>> spam ['cat', 'rat', 'elephant']
Attempting to delete a value that does not exist in the list will result in a ValueError error. For example, enter the following into the interactive shell and notice the error that is displayed:
>>> spam = ['cat', 'bat', 'rat', 'elephant'] >>> spam.remove('chicken') Traceback (most recent call last): File "<pyshell#11>", line 1, in <module> spam.remove('chicken') ValueError: list.remove(x): x not in list
If the value appears multiple times in the list, only the first instance of the value will be removed. Enter the following into the interactive shell:
>>> spam = ['cat', 'bat', 'rat', 'cat', 'hat', 'cat'] >>> spam.remove('cat') >>> spam ['bat', 'rat', 'cat', 'hat', 'cat']
The del statement is good to use when you know the index of the value you want to remove from the list. The remove() method is useful when you know the value you want to remove from the list.
Sorting the Values in a List with the sort() Method
Lists of number values or lists of strings can be sorted with the sort() method. For example, enter the following into the interactive shell:
>>> spam = [2, 5, 3.14, 1, -7] >>> spam.sort() >>> spam [-7, 1, 2, 3.14, 5] >>> spam = ['ants', 'cats', 'dogs', 'badgers', 'elephants'] >>> spam.sort() >>> spam ['ants', 'badgers', 'cats', 'dogs', 'elephants']
You can also pass True for the reverse keyword argument to have sort() sort the values in reverse order. Enter the following into the interactive shell:
>>> spam.sort(reverse=True) >>> spam ['elephants', 'dogs', 'cats', 'badgers', 'ants']
There are three things you should note about the sort() method. First, the sort() method sorts the list in place; don’t try to capture the return value by writing code like spam = spam.sort().
Second, you cannot sort lists that have both number values and string values in them, since Python doesn’t know how to compare these values. Enter the following into the interactive shell and notice the TypeError error:
>>> spam = [1, 3, 2, 4, 'Alice', 'Bob'] >>> spam.sort() Traceback (most recent call last): File "<pyshell#70>", line 1, in <module> spam.sort() TypeError: '<' not supported between instances of 'str' and 'int'
Third, sort() uses “ASCIIbetical order” rather than actual alphabetical order for sorting strings. This means uppercase letters come before lowercase letters. Therefore, the lowercase a is sorted so that it comes after the uppercase Z. For an example, enter the following into the interactive shell:
>>> spam = ['Alice', 'ants', 'Bob', 'badgers', 'Carol', 'cats'] >>> spam.sort() >>> spam ['Alice', 'Bob', 'Carol', 'ants', 'badgers', 'cats']
If you need to sort the values in regular alphabetical order, pass str.lower for the key keyword argument in the sort() method call.
>>> spam = ['a', 'z', 'A', 'Z'] >>> spam.sort(key=str.lower) >>> spam ['a', 'A', 'z', 'Z']
This causes the sort() function to treat all the items in the list as if they were lowercase without actually changing the values in the list.
Reversing the Values in a List with the reverse() Method
If you need to quickly reverse the order of the items in a list, you can call the reverse() list method. Enter the following into the interactive shell:
>>> spam = ['cat', 'dog', 'moose'] >>> spam.reverse() >>> spam ['moose', 'dog', 'cat']
EXCEPTIONS TO INDENTATION RULES IN PYTHON
In most cases, the amount of indentation for a line of code tells Python what block it is in. There are some exceptions to this rule, however. For example, lists can actually span several lines in the source code file. The indentation of these lines does not matter; Python knows that the list is not finished until it sees the ending square bracket. For example, you can have code that looks like this:
spam = ['apples', 'oranges', 'bananas', 'cats'] print(spam)
Of course, practically speaking, most people use Python’s behavior to make their lists look pretty and readable, like the messages list in the Magic 8 Ball program.
You can also split up a single instruction across multiple lines using the \ line continuation character at the end. Think of \ as saying, “This instruction continues on the next line.” The indentation on the line after a \ line continuation is not significant. For example, the following is valid Python code:
print('Four score and seven ' + \ 'years ago...')
These tricks are useful when you want to rearrange long lines of Python code to be a bit more readable.
Like the sort() list method, reverse() doesn’t return a list. This is why you write spam.reverse(), instead of spam = spam.reverse().
Using lists, you can write a much more elegant version of the previous chapter’s Magic 8 Ball program. Instead of several lines of nearly identical elif statements, you can create a single list that the code works with. Open a new file editor window and enter the following code. Save it as magic8Ball2.py.
import random messages = ['It is certain', 'It is decidedly so', 'Yes definitely', 'Reply hazy try again', 'Ask again later', 'Concentrate and ask again', 'My reply is no', 'Outlook not so good', 'Very doubtful'] print(messages[random.randint(0, len(messages) - 1)])
You can view the execution of this program at https://autbor.com/magic8ball2/.
When you run this program, you’ll see that it works the same as the previous magic8Ball.py program.
Notice the expression you use as the index for messages: random.randint (0, len(messages) - 1). This produces a random number to use for the index, regardless of the size of messages. That is, you’ll get a random number between 0 and the value of len(messages) - 1. The benefit of this approach is that you can easily add and remove strings to the messages list without changing other lines of code. If you later update your code, there will be fewer lines you have to change and fewer chances for you to introduce bugs.
Lists aren’t the only data types that represent ordered sequences of values. For example, strings and lists are actually similar if you consider a string to be a “list” of single text characters. The Python sequence data types include lists, strings, range objects returned by range(), and tuples (explained in the “The Tuple Data Type” on page 96). Many of the things you can do with lists can also be done with strings and other values of sequence types: indexing; slicing; and using them with for loops, with len(), and with the in and not in operators. To see this, enter the following into the interactive shell:
>>> name = 'Zophie' >>> name[0] 'Z' >>> name[-2] 'i' >>> name[0:4] 'Zoph' >>> 'Zo' in name True >>> 'z' in name False >>> 'p' not in name False >>> for i in name: ... print('* * * ' + i + ' * * *') * * * Z * * * * * * o * * * * * * p * * * * * * h * * * * * * i * * * * * * e * * *
Mutable and Immutable Data Types
But lists and strings are different in an important way. A list value is a mutable data type: it can have values added, removed, or changed. However, a string is immutable: it cannot be changed. Trying to reassign a single character in a string results in a TypeError error, as you can see by entering the following into the interactive shell:
>>> name = 'Zophie a cat' >>> name[7] = 'the' Traceback (most recent call last): File "<pyshell#50>", line 1, in <module> name[7] = 'the' TypeError: 'str' object does not support item assignment
The proper way to “mutate” a string is to use slicing and concatenation to build a new string by copying from parts of the old string. Enter the following into the interactive shell:
>>> name = 'Zophie a cat' >>> newName = name[0:7] + 'the' + name[8:12] >>> name 'Zophie a cat' >>> newName 'Zophie the cat'
We used [0:7] and [8:12] to refer to the characters that we don’t wish to replace. Notice that the original 'Zophie a cat' string is not modified, because strings are immutable.
Although a list value is mutable, the second line in the following code does not modify the list eggs:
>>> eggs = [1, 2, 3] >>> eggs = [4, 5, 6] >>> eggs [4, 5, 6]
The list value in eggs isn’t being changed here; rather, an entirely new and different list value ([4, 5, 6]) is overwriting the old list value ([1, 2, 3]). This is depicted in Figure 4-2.
If you wanted to actually modify the original list in eggs to contain [4, 5, 6], you would have to do something like this:
Figure 4-2: When eggs = [4, 5, 6] is executed, the contents of eggs are replaced with a new list value.
Figure 4-3: The del statement and the append() method modify the same list value in place.
Changing a value of a mutable data type (like what the del statement and append() method do in the previous example) changes the value in place, since the variable’s value is not replaced with a new list value.
Mutable versus immutable types may seem like a meaningless distinction, but “Passing References” on page 100 will explain the different behavior when calling functions with mutable arguments versus immutable arguments. But first, let’s find out about the tuple data type, which is an immutable form of the list data type.
The Tuple Data Type
The tuple data type is almost identical to the list data type, except in two ways. First, tuples are typed with parentheses, ( and ), instead of square brackets, [ and ]. For example, enter the following into the interactive shell:
>>> eggs = ('hello', 42, 0.5) >>> eggs[0] 'hello' >>> eggs[1:3] (42, 0.5) >>> len(eggs) 3
But the main way that tuples are different from lists is that tuples, like strings, are immutable. Tuples cannot have their values modified, appended, or removed. Enter the following into the interactive shell, and look at the TypeError error message:
>>> eggs = ('hello', 42, 0.5) >>> eggs[1] = 99 Traceback (most recent call last): File "<pyshell#5>", line 1, in <module> eggs[1] = 99 TypeError: 'tuple' object does not support item assignment
If you have only one value in your tuple, you can indicate this by placing a trailing comma after the value inside the parentheses. Otherwise, Python will think you’ve just typed a value inside regular parentheses. The comma is what lets Python know this is a tuple value. (Unlike some other programming languages, it’s fine to have a trailing comma after the last item in a list or tuple in Python.) Enter the following type() function calls into the interactive shell to see the distinction:
>>> type(('hello',)) <class 'tuple'> >>> type(('hello')) <class 'str'>
You can use tuples to convey to anyone reading your code that you don’t intend for that sequence of values to change. If you need an ordered sequence of values that never changes, use a tuple. A second benefit of using tuples instead of lists is that, because they are immutable and their contents don’t change, Python can implement some optimizations that make code using tuples slightly faster than code using lists.
Converting Types with the list() and tuple() Functions
Just like how str(42) will return '42', the string representation of the integer 42, the functions list() and tuple() will return list and tuple versions of the values passed to them. Enter the following into the interactive shell, and notice that the return value is of a different data type than the value passed:
>>> tuple(['cat', 'dog', 5]) ('cat', 'dog', 5) >>> list(('cat', 'dog', 5)) ['cat', 'dog', 5] >>> list('hello') ['h', 'e', 'l', 'l', 'o']
Converting a tuple to a list is handy if you need a mutable version of a tuple value.
As you’ve seen, variables “store” strings and integer values. However, this explanation is a simplification of what Python is actually doing. Technically, variables are storing references to the computer memory locations where the values are stored. Enter the following into the interactive shell:
>>> spam = 42 >>> cheese = spam >>> spam = 100 >>> spam 100 >>> cheese 42
When you assign 42 to the spam variable, you are actually creating the 42 value in the computer’s memory and storing a reference to it in the spam variable. When you copy the value in spam and assign it to the variable cheese, you are actually copying the reference. Both the spam and cheese variables refer to the 42 value in the computer’s memory. When you later change the value in spam to 100, you’re creating a new 100 value and storing a reference to it in spam. This doesn’t affect the value in cheese. Integers are immutable values that don’t change; changing the spam variable is actually making it refer to a completely different value in memory.
But lists don’t work this way, because list values can change; that is, lists are mutable. Here is some code that will make this distinction easier to understand. Enter this into the interactive shell:
➊ >>> spam = [0, 1, 2, 3, 4, 5] ➋ >>> cheese = spam # The reference is being copied, not the list. ➌ >>> cheese[1] = 'Hello!' # This changes the list value. >>> spam [0, 'Hello!', 2, 3, 4, 5] >>> cheese # The cheese variable refers to the same list. [0, 'Hello!', 2, 3, 4, 5]
This might look odd to you. The code touched only the cheese list, but it seems that both the cheese and spam lists have changed.
When you create the list ➊, you assign a reference to it in the spam variable. But the next line ➋ copies only the list reference in spam to cheese, not the list value itself. This means the values stored in spam and cheese now both refer to the same list. There is only one underlying list because the list itself was never actually copied. So when you modify the first element of cheese ➌, you are modifying the same list that spam refers to.
Figure 4-4: spam = [0, 1, 2, 3, 4, 5] stores a reference to a list, not the actual list.
Figure 4-5: spam = cheese copies the reference, not the list.
Figure 4-6: cheese[1] = 'Hello!' modifies the list that both variables refer to.
Although Python variables technically contain references to values, people often casually say that the variable contains the value.
Identity and the id() Function
You may be wondering why the weird behavior with mutable lists in the previous section doesn’t happen with immutable values like integers or strings. We can use Python’s id() function to understand this. All values in Python have a unique identity that can be obtained with the id() function. Enter the following into the interactive shell:
>>> id('Howdy') # The returned number will be different on your machine. 44491136
When Python runs id('Howdy'), it creates the 'Howdy' string in the computer’s memory. The numeric memory address where the string is stored is returned by the id() function. Python picks this address based on which memory bytes happen to be free on your computer at the time, so it’ll be different each time you run this code.
Like all strings, 'Howdy' is immutable and cannot be changed. If you “change” the string in a variable, a new string object is being made at a different place in memory, and the variable refers to this new string. For example, enter the following into the interactive shell and see how the identity of the string referred to by bacon changes:
>>> bacon = 'Hello' >>> id(bacon) 44491136 >>> bacon += ' world!' # A new string is made from 'Hello' and ' world!'. >>> id(bacon) # bacon now refers to a completely different string. 44609712
However, lists can be modified because they are mutable objects. The append() method doesn’t create a new list object; it changes the existing list object. We call this “modifying the object in-place.”
>>> eggs = ['cat', 'dog'] # This creates a new list. >>> id(eggs) 35152584 >>> eggs.append('moose') # append() modifies the list "in place". >>> id(eggs) # eggs still refers to the same list as before. 35152584 >>> eggs = ['bat', 'rat', 'cow'] # This creates a new list, which has a new identity. >>> id(eggs) # eggs now refers to a completely different list. 44409800
If two variables refer to the same list (like spam and cheese in the previous section) and the list value itself changes, both variables are affected because they both refer to the same list. The append(), extend(), remove(), sort(), reverse(), and other list methods modify their lists in place.
Python’s automatic garbage collector deletes any values not being referred to by any variables to free up memory. You don’t need to worry about how the garbage collector works, which is a good thing: manual memory management in other programming languages is a common source of bugs.
Passing References
References are particularly important for understanding how arguments get passed to functions. When a function is called, the values of the arguments are copied to the parameter variables. For lists (and dictionaries, which I’ll describe in the next chapter), this means a copy of the reference is used for the parameter. To see the consequences of this, open a new file editor window, enter the following code, and save it as passingReference.py:
def eggs(someParameter): someParameter.append('Hello') spam = [1, 2, 3] eggs(spam) print(spam)
Notice that when eggs() is called, a return value is not used to assign a new value to spam. Instead, it modifies the list in place, directly. When run, this program produces the following output:
[1, 2, 3, 'Hello']
Even though spam and someParameter contain separate references, they both refer to the same list. This is why the append('Hello') method call inside the function affects the list even after the function call has returned.
Keep this behavior in mind: forgetting that Python handles list and dictionary variables this way can lead to confusing bugs.
The copy Module’s copy() and deepcopy() Functions
Although passing around references is often the handiest way to deal with lists and dictionaries, if the function modifies the list or dictionary that is passed, you may not want these changes in the original list or dictionary value. For this, Python provides a module named copy that provides both the copy() and deepcopy() functions. The first of these, copy.copy(), can be used to make a duplicate copy of a mutable value like a list or dictionary, not just a copy of a reference. Enter the following into the interactive shell:
>>> import copy >>> spam = ['A', 'B', 'C', 'D'] >>> id(spam) 44684232 >>> cheese = copy.copy(spam) >>> id(cheese) # cheese is a different list with different identity. 44685832 >>> cheese[1] = 42 >>> spam ['A', 'B', 'C', 'D'] >>> cheese ['A', 42, 'C', 'D']
Figure 4-7: cheese = copy.copy(spam) creates a second list that can be modified independently of the first.
If the list you need to copy contains lists, then use the copy.deepcopy() function instead of copy.copy(). The deepcopy() function will copy these inner lists as well.
Figure 4-8: Four steps in a Conway’s Game of Life simulation
Even though the rules are simple, there are many surprising behaviors that emerge. Patterns in Conway’s Game of Life can move, self-replicate, or even mimic CPUs. But at the foundation of all of this complex, advanced behavior is a rather simple program.
We can use a list of lists to represent the two-dimensional field. The inner list represents each column of squares and stores a '#' hash string for living squares and a ' ' space string for dead squares. Type the following source code into the file editor, and save the file as conway.py. It’s fine if you don’t quite understand how all of the code works; just enter it and follow along with comments and explanations provided here as close as you can:
Next, we need to use two nested for loops to calculate each cell for the next step. The living or dead state of the cell depends on the neighbors, so let’s first calculate the index of the cells to the left, right, above, and below the current x- and y-coordinates.
The % mod operator performs a “wraparound.” The left neighbor of a cell in the leftmost column 0 would be 0 - 1 or -1. To wrap this around to the rightmost column’s index, 59, we calculate (0 - 1) % WIDTH. Since WIDTH is 60, this expression evaluates to 59. This mod-wraparound technique works for the right, above, and below neighbors as well.
# Count number of living neighbors: numNeighbors = 0 if currentCells[leftCoord][aboveCoord] == '#': numNeighbors += 1 # Top-left neighbor is alive. if currentCells[x][aboveCoord] == '#': numNeighbors += 1 # Top neighbor is alive. if currentCells[rightCoord][aboveCoord] == '#': numNeighbors += 1 # Top-right neighbor is alive. if currentCells[leftCoord][y] == '#': numNeighbors += 1 # Left neighbor is alive. if currentCells[rightCoord][y] == '#': numNeighbors += 1 # Right neighbor is alive. if currentCells[leftCoord][belowCoord] == '#': numNeighbors += 1 # Bottom-left neighbor is alive. if currentCells[x][belowCoord] == '#': numNeighbors += 1 # Bottom neighbor is alive. if currentCells[rightCoord][belowCoord] == '#': numNeighbors += 1 # Bottom-right neighbor is alive.
To decide if the cell at nextCells[x][y] should be living or dead, we need to count the number of living neighbors currentCells[x][y] has. This series of if statements checks each of the eight neighbors of this cell, and adds 1 to numNeighbors for each living one.
# Set cell based on Conway's Game of Life rules: if currentCells[x][y] == '#' and (numNeighbors == 2 or numNeighbors == 3): # Living cells with 2 or 3 neighbors stay alive: nextCells[x][y] = '#' elif currentCells[x][y] == ' ' and numNeighbors == 3: # Dead cells with 3 neighbors become alive: nextCells[x][y] = '#' else: # Everything else dies or stays dead: nextCells[x][y] = ' ' time.sleep(1) # Add a 1-second pause to reduce flickering.
Now that we know the number of living neighbors for the cell at currentCells[x][y], we can set nextCells[x][y] to either '#' or ' '. After we loop over every possible x- and y-coordinate, the program takes a 1-second pause by calling time.sleep(1). Then the program execution goes back to the start of the main program loop to continue with the next step.
Several patterns have been discovered with names such as “glider,” “propeller,” or “heavyweight spaceship.” The glider pattern, pictured in Figure 4-8, results in a pattern that “moves” diagonally every four steps. You can create a single glider by replacing this line in our conway.py program:
if random.randint(0, 1) == 0:
with this line:
if (x, y) in ((1, 0), (2, 1), (0, 2), (1, 2), (2, 2)):
You can find out more about the intriguing devices made using Conway’s Game of Life by searching the web. And you can find other short, text-based Python programs like this one at https://github.com/asweigart/pythonstdiogames.
Lists are useful data types since they allow you to write code that works on a modifiable number of values in a single variable. Later in this book, you will see programs using lists to do things that would be difficult or impossible to do without them.
Lists are a sequence data type that is mutable, meaning that their contents can change. Tuples and strings, though also sequence data types, are immutable and cannot be changed. A variable that contains a tuple or string value can be overwritten with a new tuple or string value, but this is not the same thing as modifying the existing value in place—like, say, the append() or remove() methods do on lists.
Variables do not store list values directly; they store references to lists. This is an important distinction when you are copying variables or passing lists as arguments in function calls. Because the value that is being copied is the list reference, be aware that any changes you make to the list might impact another variable in your program. You can use copy() or deepcopy() if you want to make changes to a list in one variable without modifying the original list.
1. What is []?
2. How would you assign the value 'hello' as the third value in a list stored in a variable named spam? (Assume spam contains [2, 4, 6, 8, 10].)
For the following three questions, let’s say spam contains the list ['a', 'b', 'c', 'd'].
3. What does spam[int(int('3' * 2) // 11)] evaluate to?
4. What does spam[-1] evaluate to?
5. What does spam[:2] evaluate to?
For the following three questions, let’s say bacon contains the list [3.14, 'cat', 11, 'cat', True].
6. What does bacon.index('cat') evaluate to?
7. What does bacon.append(99) make the list value in bacon look like?
8. What does bacon.remove('cat') make the list value in bacon look like?
9. What are the operators for list concatenation and list replication?
10. What is the difference between the append() and insert() list methods?
11. What are two ways to remove values from a list?
12. Name a few ways that list values are similar to string values.
13. What is the difference between lists and tuples?
14. How do you type the tuple value that has just the integer value 42 in it?
15. How can you get the tuple form of a list value? How can you get the list form of a tuple value?
16. Variables that “contain” list values don’t actually contain lists directly. What do they contain instead?
17. What is the difference between copy.copy() and copy.deepcopy()?
For practice, write programs to do the following tasks.
Comma Code
Say you have a list value like this:
spam = ['apples', 'bananas', 'tofu', 'cats']
Write a function that takes a list value as an argument and returns a string with all the items separated by a comma and a space, with and inserted before the last item. For example, passing the previous spam list to the function would return 'apples, bananas, tofu, and cats'. But your function should be able to work with any list value passed to it. Be sure to test the case where an empty list [] is passed to your function.
Coin Flip Streaks
For this exercise, we’ll try doing an experiment. If you flip a coin 100 times and write down an “H” for each heads and “T” for each tails, you’ll create a list that looks like “T T T T H H H H T T.” If you ask a human to make up 100 random coin flips, you’ll probably end up with alternating head-tail results like “H T H T H H T H T T,” which looks random (to humans), but isn’t mathematically random. A human will almost never write down a streak of six heads or six tails in a row, even though it is highly likely to happen in truly random coin flips. Humans are predictably bad at being random.
Write a program to find out how often a streak of six heads or a streak of six tails comes up in a randomly generated list of heads and tails. Your program breaks up the experiment into two parts: the first part generates a list of randomly selected 'heads' and 'tails' values, and the second part checks if there is a streak in it. Put all of this code in a loop that repeats the experiment 10,000 times so we can find out what percentage of the coin flips contains a streak of six heads or tails in a row. As a hint, the function call random.randint(0, 1) will return a 0 value 50% of the time and a 1 value the other 50% of the time.
You can start with the following template:
import random numberOfStreaks = 0 for experimentNumber in range(10000): # Code that creates a list of 100 'heads' or 'tails' values. # Code that checks if there is a streak of 6 heads or tails in a row. print('Chance of streak: %s%%' % (numberOfStreaks / 100))
Of course, this is only an estimate, but 10,000 is a decent sample size. Some knowledge of mathematics could give you the exact answer and save you the trouble of writing a program, but programmers are notoriously bad at math.
Character Picture Grid
Say you have a list of lists where each value in the inner lists is a one-character string, like this:
grid = [['.', '.', '.', '.', '.', '.'], ['.', 'O', 'O', '.', '.', '.'], ['O', 'O', 'O', 'O', '.', '.'], ['O', 'O', 'O', 'O', 'O', '.'], ['.', 'O', 'O', 'O', 'O', 'O'], ['O', 'O', 'O', 'O', 'O', '.'], ['O', 'O', 'O', 'O', '.', '.'], ['.', 'O', 'O', '.', '.', '.'], ['.', '.', '.', '.', '.', '.']]
Think of grid[x][y] as being the character at the x- and y-coordinates of a “picture” drawn with text characters. The (0, 0) origin is in the upper-left corner, the x-coordinates increase going right, and the y-coordinates increase going down.
Copy the previous grid value, and write code that uses it to print the image.
..OO.OO.. .OOOOOOO. .OOOOOOO. ..OOOOO.. ...OOO... ....O....
Hint: You will need to use a loop in a loop in order to print grid[0][0], then grid[1][0], then grid[2][0], and so on, up to grid[8][0]. This will finish the first row, so then print a newline. Then your program should print grid[0][1], then grid[1][1], then grid[2][1], and so on. The last thing your program will print is grid[8][5].
Also, remember to pass the end keyword argument to print() if you don’t want a newline printed automatically after each print() call.
The operator [n:m]
returns the part of the sequence from the n’th element to the m’th element, including the first but excluding the last. This behavior is counter-intuitive; it makes more sense if you imagine the indices pointing between the characters, as in the following diagram:
There are two possible states:
or
>>> eggs = [1, 2, 3] >>> del eggs[2] >>> del eggs[1] >>> del eggs[0] >>> eggs.append(4) >>> eggs.append(5) >>> eggs.append(6) >>> eggs [4, 5, 6]
In the first example, the list value that eggs ends up with is the same list value it started with. It’s just that this list has been changed, rather than overwritten. Figure 4-3 depicts the seven changes made by the first seven lines in the previous interactive shell example.
Remember that variables are like boxes that contain values. The previous figures in this chapter show that lists in boxes aren’t exactly accurate, because list variables don’t actually contain lists—they contain references to lists. (These references will have ID numbers that Python uses internally, but you can ignore them.) Using boxes as a metaphor for variables, Figure 4-4 shows what happens when a list is assigned to the spam variable.
Then, in Figure 4-5, the reference in spam is copied to cheese. Only a new reference was created and stored in cheese, not a new list. Note how both references refer to the same list.
When you alter the list that cheese refers to, the list that spam refers to is also changed, because both cheese and spam refer to the same list. You can see this in Figure 4-6.
Now the spam and cheese variables refer to separate lists, which is why only the list in cheese is modified when you assign 42 at index 1. As you can see in Figure 4-7, the reference ID numbers are no longer the same for both variables because the variables refer to independent lists.
Conway’s Game of Life is an example of cellular automata: a set of rules governing the behavior of a field made up of discrete cells. In practice, it creates a pretty animation to look at. You can draw out each step on graph paper, using the squares as cells. A filled-in square will be “alive” and an empty square will be “dead.” If a living square has two or three living neighbors, it continues to live on the next step. If a dead square has exactly three living neighbors, it comes alive on the next step. Every other square dies or remains dead on the next step. You can see an example of the progression of steps in Figure 4-8.
Augmented assignment statement
Equivalent assignment statement
spam += 1
spam = spam + 1
spam -= 1
spam = spam - 1
spam *= 1
spam = spam * 1
spam /= 1
spam = spam / 1
spam %= 1
spam = spam % 1