1. Review: You can use [ ] to make lists in python

A Python list is a sequence of values, which are called elements of the list. A list can be created by writing a sequence of comma-separated expressions delimited by square brackets.

Lists themselves are Python objects, and so can be named by variables.

Below are some homogenous lists, which means that all elements of the list have the same type.

In [1]:
primes = [2, 3, 5, 7, 11, 13, 17, 19]  
states = ['Alabama', 'Michigan', 'California', 'Wyoming'] 
somebools = [1<2, 1==2, 1>2]           
somenums = [3+4, 5*6, 7-9, 50/5]       
somestrings = ['ab' + 'cd', 'ma'*4] 
anEmptyList = [] # A string with zero elements 

Note: If an element is an expression, such as 1<2 or 3+4, it's first evaluated to a value, and then stored in the list.
So, can you guess what is stored in somebools, somenums, and somestrings?

In [2]:
somebools
Out[2]:
[True, False, False]
In [3]:
somenums
Out[3]:
[7, 30, -2, 10.0]
In [4]:
somestrings
Out[4]:
['abcd', 'mamamama']
In [5]:
type(primes)
Out[5]:
list
In [6]:
type(states)
Out[6]:
list

2. Review: Indexing lists

To access individual elements of the list as opposed to the entire list, we use indices.

Very Important Note: The nonnegative indices always start at 0 and end at a number one less than the length of the list.

In [7]:
primes = [2, 3, 5, 7, 11, 13, 17, 19] 

Accessing the first element of the list, using index 0:

In [8]:
primes[0]
Out[8]:
2
In [9]:
# how do you access 11 in the list?
# Your code here
primes[4]
Out[9]:
11

What happens if you write primes[8]? Can you explain it?

In [10]:
primes[8]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-10-637270b50f3f> in <module>()
----> 1 primes[8]

IndexError: list index out of range

Now that we reviewed how lists are created and indexed via Python code, let's discuss a model of how lists are stored in the computer memory.

3. Model: List Memory Diagrams

We draw lists with n elements as a big box containing n smaller boxes (slots) that hold the n elements. Each smaller box or slot is written with an index number above the box. Organizing lists with these diagrams help us understand ways to interact with lists.

Below are two examples of memory diagrams for two different lists. Each list is assigned to a variable. To depict a variable using memory diagrams we simply write the name of the variable and a box to the right. When the value of the variable is a list, we draw an arrow from the variable box to the big box representing the list.

A picture depicting the memory diagram of the list assigned to the variable `primes` from above.  The variable name `primes` is on the left and there is a box directly to the right of primes with an arrow pointing from the box to a much larger box containing all the slots of the list.  In the larger box are eight smaller boxes or slots each labelled with the numbers 0, 1, 2, 3, 4, 5, 6, and 7 above from left to right to represent the indices of the list.  The values in those boxes from left to right are 2, 3, 5, 7, 11, 13, 17 and 19.  The picture also indicates that we could also label the indices for the smaller boxes using negative numbers.  Any sequence can be referred to by positive or negative indices.  In this example, the negative indices from left to right would be -8, -7, -6, -5, -4, -3, -2, and -1.

A picture depicting the memory diagram of the list assigned to the variable `bools` from above.  The variable name `bools` is on the left and there is a box directly to the right of hourses with an arrow pointing from the box to a much larger box containing all the slots of the list.  In the larger box are three smaller boxes or slots each labelled with the numbers 0, 1, and 2 above from left to right to represent the indices of the list.  The values in each of the small boxes from left to right are True, False, and False.

For a length-n list, slots can also be addressed by indices that go (right to left) from -1 to -n.

In [11]:
primes[-2]
Out[11]:
17
In [12]:
# give two different ways to access the number 13 (using positive and negative indices)
# Your code here
primes[5]
primes[-3]
Out[12]:
13

4. Diagrams and Indexing with Nested Lists

Often, we will work with lists of lists (or nested lists). We can create and index them similarly to simple lists

In [13]:
# Let's create some nested lists

animalLists = [['fox', 'raccoon'], ['duck', 'raven', 'gosling'], [], ['turkey']]

lottaNumbers = [[18, 19, 20], [5, 34, 2], [3], [10, 20], [37, 54]]

This picture depicts a memory diagram for the list animalLists.  On the left side of the picture is the variable name animalLists with a box directly to the right.  There is an arrow from that box to a much larger box which contains the elements of the outer list.  That outer list has four elements, all of which are also lists, and therefore there are four slots with indices 0, 1, 2, and 3.  Because the contents of the outer list are lists, we draw an arrow from the each respective slot to its diagram of its sublist.  Therefore, we have four large boxes for each sublist each of which has slots for its string elements.  The strings are too large to fit in the small boxes, so we simply draw arrows to the strings from their respective slots in each sublist diagram.

We can assign list elements to new variables:

In [14]:
mammals = animalLists[0]
mammals
Out[14]:
['fox', 'raccoon']
In [15]:
mammals[1]
Out[15]:
'raccoon'
In [16]:
animalLists[0][1]
Out[16]:
'raccoon'

We just showed two different ways to refer to the same value, "raccoon".

In [17]:
animalLists[1][0]
Out[17]:
'duck'
In [18]:
# What indices return "turkey" in animalLists[???][???]
# Your code here
animalLists[3][0]
Out[18]:
'turkey'
In [19]:
# What indices return "gosling" in animalLists[???][???]
# Your code here
animalLists[1][2]
Out[19]:
'gosling'

5. Some other ways to make lists (some you've seen before)

In [20]:
# Using list on a range()
oddies = list(range(1,10,2))  

# Using split() on a string
lyrics = 'call me on my cell'.split()

# Using list on a string
letters = list("happy")

# By concatenation other lists
ints = [7, 2, 4] + [8, 5] + [1, 0, 9, 3]

# By repeating a list
reps = [7, 2, 4] * 3
In [21]:
print(oddies)
print(lyrics)
print(letters)
print(ints)
print(reps)
[1, 3, 5, 7, 9]
['call', 'me', 'on', 'my', 'cell']
['h', 'a', 'p', 'p', 'y']
[7, 2, 4, 8, 5, 1, 0, 9, 3]
[7, 2, 4, 7, 2, 4, 7, 2, 4]

6. You can mix and match different types within a list (heterogeneous lists)

Here's a heterogeneous list, containing several different types of elements.

In [22]:
stuff = [17, True, 'Wendy', None, [42, False, 'computer']] 

For you: Try to predict the value for each of the following expressions:

In [23]:
stuff[0] + stuff[4][0]
Out[23]:
59
In [24]:
stuff[2] + stuff[4][2]
Out[24]:
'Wendycomputer'
In [25]:
stuff[0] + stuff[4][2]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-25-adcec19d794f> in <module>()
----> 1 stuff[0] + stuff[4][2]

TypeError: unsupported operand type(s) for +: 'int' and 'str'

7. Lists are sequences

len: Length of a list

We will use the familiar built-in function len() to find the length of a list.

In [26]:
len(primes)
Out[26]:
8
In [27]:
# Can you guess what the length of this nested list will be?

animalLists = [['fox', 'raccoon'], ['duck', 'raven', 'gosling'], [], ['turkey']]
In [28]:
# Check your guess
len(animalLists)
Out[28]:
4

What about:

In [29]:
len(animalLists[1])
Out[29]:
3
In [30]:
len(lottaNumbers)
Out[30]:
5
In [31]:
len(stuff)
Out[31]:
5
In [32]:
len([])
Out[32]:
0

List slicing

Use the slicing operator : to create a new list that contains some of the elements of the original list.

In [33]:
primes
Out[33]:
[2, 3, 5, 7, 11, 13, 17, 19]
In [34]:
primes[2:6]
Out[34]:
[5, 7, 11, 13]
In [35]:
primes[2:]
Out[35]:
[5, 7, 11, 13, 17, 19]
In [36]:
primes[1:7:2]
Out[36]:
[3, 7, 13]
In [37]:
# First slice index defaults to 0 when step is positive
primes[:5:2]
Out[37]:
[2, 5, 11]
In [38]:
# Second slice index defaults to length of list when step is positive
primes[3::2]
Out[38]:
[7, 13, 19]
In [39]:
# Can have negative step
primes[7:1:-2]
Out[39]:
[19, 13, 7]
In [40]:
primes[-1:-8:-1]
Out[40]:
[19, 17, 13, 11, 7, 5, 3]
In [41]:
# What are default first and second indices when step is negative?
primes[::-1]
Out[41]:
[19, 17, 13, 11, 7, 5, 3, 2]

Other sequence operations

In [42]:
13 in primes
Out[42]:
True
In [43]:
15 in primes
Out[43]:
False
In [44]:
15 not in primes
Out[44]:
True
In [45]:
min([6,2,3,9,5,8])
Out[45]:
2
In [46]:
max([6,2,3,9,5,8])
Out[46]:
9
In [47]:
max(['one', 'two', 'three','four', 'five'])
Out[47]:
'two'
In [48]:
# Lists are compared in dictionary order, where each element is treated like a "character"
[5,3] < [6, 2, 4]
Out[48]:
True
In [49]:
[5,3] < [5, 2, 4]
Out[49]:
False
In [50]:
# Explain this result: 
max(animalLists)
Out[50]:
['turkey']

8. Lists are mutable

In this case mutation means:

  • we can change the contents of the list slots
  • we can change the structure of the list by adding and removing list slots. We will see three list methods for doing this: append, pop, and insert.

Let's start with myList

In [51]:
myList = [17, 3.141, True, None, ['I', 'am', 'Sam']]  
myList
Out[51]:
[17, 3.141, True, None, ['I', 'am', 'Sam']]

We can change the contents of list slots via the assignment operator =

In [52]:
myList[1] = myList[0] + 6

myList[3] = myList[0] > myList[1]

myList[4][1] = 'was'

myList # to see contents of myList
Out[52]:
[17, 23, True, False, ['I', 'was', 'Sam']]

We change the list structure by appending a new item to the end

In [53]:
myList.append(42)

myList[4].append('Adams')

myList # to see what is in there
Out[53]:
[17, 23, True, False, ['I', 'was', 'Sam', 'Adams'], 42]

pop examples

A method to remove items from a list.

Let's see first what's in the list myList, which was already mutated in the previous cells in this Notebook:

In [54]:
myList
Out[54]:
[17, 23, True, False, ['I', 'was', 'Sam', 'Adams'], 42]
In [55]:
myList.pop(1)
Out[55]:
23

Notice that pop returns the value that it removed from the list.

In [56]:
myList
Out[56]:
[17, True, False, ['I', 'was', 'Sam', 'Adams'], 42]
In [57]:
myList[3].pop(2)
Out[57]:
'Sam'
In [58]:
myList
Out[58]:
[17, True, False, ['I', 'was', 'Adams'], 42]
In [59]:
myList.pop()
Out[59]:
42

Notice that when we don't use an argument for pop, by default it removes the very last element.

In [60]:
myList
Out[60]:
[17, True, False, ['I', 'was', 'Adams']]

insert examples

insert is a method that takes two parameters:

  1. the index of the list slot where the insertion will hapen;
  2. the value to be inserted
In [61]:
myList.insert(0, 98.6)
In [62]:
myList
Out[62]:
[98.6, 17, True, False, ['I', 'was', 'Adams']]
In [63]:
myList[4].insert(2, 'not')
In [64]:
myList
Out[64]:
[98.6, 17, True, False, ['I', 'was', 'not', 'Adams']]

Aliasing

Aliasing creates another variable name for the same object in Python. We can depict these with memory diagrams by simply drawing another box for the variable with the variable name on the left. The contents of the variable box will have an arrow pointing to that object.

Aliasing Example 1

Use memory diagrams to explain the the list structures that result from executing the following statements in order

In [65]:
L1 = [7, 4]
L2 = [7, 4]
L3 = L2
L2[1] = 8
L4 = [L1, L1, L2, L3]
L4[2].append(5)

The final list diagram is the one below. A key feature of this diagram is aliasing, which means that the same list objects can be accessed by different expressions involving variables and lists. For example, the expressions L1, L4[0], and L4[1] all refer to the same 2-element list object [7,4], while the expressions L2, L3, L4[2], and L4[3] all refer to the same 3-element list object [7,8,5]. Memory diagrams involving lists are essential for understanding aliasing, which is essential for understanding how operations that mutate lists will behave.

This picture depicts a memory diagram for the aliasing example involving L1, L2, L3, L4, and L4. In the final diagram L1 is a list of the two elements 7 and 4, and L2 and L3 are aliases for the same three-element list with elements 7, 8, and 5. L4 is a four-element list: L4[0] and L4[1] are aliases for L1, and L4[2] and L4[3] are aliases for L2.

== tests for structural equality, but not aliasing

When used to compare two lists, Python's equality operator == returns True if and only if (1) the two list have the same length and (2) all the corresponding indices have values for which == returns True. This is called structural equality of lists.

As shown below, two lists can be structurally equal via == even if they are not aliases of the same lists. So == is too weak a test for determining aliases:

In [66]:
L1b = [7, 4]
L2b = [7, 4]
L3b = L2b

print('L1b==L2b', L1b==L2b)
print('L1b==L3b', L1b==L3b)
print('L2b==L3b', L2b==L3b)
L1b==L2b True
L1b==L3b True
L2b==L3b True

Python's built-in id function

Python's id() function returns a long number that is a unique identifier for each object in a memory diagram. You can think of it as being the abstract address of the object in memory. If two list expressions have the same id, they must be represented by a single list box in a memory diagram (i.e., they are aliases for the same list value). If two list expressions have different ids, they must be represented by two different list boxes in a memory diagram.

For example, id can tell us that L2b and L3b are aliases for the same list, but that L1b is a different list:

In [67]:
print('id(L1b) =>', id(L1b))
print('id(L2b) =>', id(L2b))
print('id(L3b) =>', id(L3b))
id(L1b) => 4407941320
id(L2b) => 4407940680
id(L3b) => 4407940680

In our bigger running example, because L1, L4[0], and L4[1] are aliases of the same list, all return the same number using the id function:

In [68]:
print('id(L1) =>', id(L1))
print('id(L4[0]) =>', id(L4[0]))
print('id(L4[1]) =>', id(L4[1]))
id(L1) => 4408072072
id(L4[0]) => 4408072072
id(L4[1]) => 4408072072

Because L2, L3, L4[2], and L4[2] are aliases of a list that is different than L1, all share an id that is different that the id for L1:

In [69]:
print('id(L2) =>', id(L2))
print('id(L3) =>', id(L3))
print('id(L4[2]) =>', id(L4[2]))
print('id(L4[3]) =>', id(L4[3]))
id(L2) => 4407939144
id(L3) => 4407939144
id(L4[2]) => 4407939144
id(L4[3]) => 4407939144

For this reason, id is handy for reasoning about aliasing/sharing in memory diagrams.

Python's is operator

Python's binary is operator returns True if its two operands have the same id; otherwise it returns False. It is also very useful for reasoning about aliasing/sharing in memory diagrams.

In the small example, is shows that L2b and L3b are the same list, but they are different from L1b:

In [70]:
print('L1b is L2b =>', L1b is L2b)
print('L1b is L3b =>', L1b is L3b)
print('L2b is L3b =>', L2b is L3b)
L1b is L2b => False
L1b is L3b => False
L2b is L3b => True

In the larger running example, it shows which expressions are aliases for the same lists:

In [71]:
print('L1 is L2 =>', L1 is L2)
print('L2 is L3 =>', L2 is L3)
print('L1 is L4[0] =>', L1 is L4[0])
print('L1 is L4[1] =>', L1 is L4[1])
print('L1 is L4[2] =>', L1 is L4[2])
print('L2 is L4[1] =>', L2 is L4[1])
print('L2 is L4[2] =>', L2 is L4[2])
print('L2 is L4[3] =>', L2 is L4[3])
print('L4[0] is L4[1] =>', L4[0] is L4[1])
print('L4[1] is L4[2] =>', L4[1] is L4[2])
print('L4[2] is L4[3] =>', L4[2] is L4[3])
L1 is L2 => False
L2 is L3 => True
L1 is L4[0] => True
L1 is L4[1] => True
L1 is L4[2] => False
L2 is L4[1] => False
L2 is L4[2] => True
L2 is L4[3] => True
L4[0] is L4[1] => True
L4[1] is L4[2] => False
L4[2] is L4[3] => True

Aliasing Example 2

As a second example of aliasing, consider modifications to the memory diagram at the end of the insert examples:

This picture depicts a memory diagram for from the end of the insert examples. There is a single variable named myList that holds a list with 5 slots. The contents of the first four slots are 98.6, 17, 23, and True. The fifth slot holds a four-element sublist with the four string values 'I', 'am', 'not', and 'Adams'.

We begin by introducting the alias list2 for myList:

In [72]:
list2 = myList
In [73]:
id(list2)
Out[73]:
4407543560
In [74]:
id(myList)
Out[74]:
4407543560
In [75]:
list2 is myList
Out[75]:
True

We also inroduce adamsList as an alias for list2[4]

In [76]:
adamsList = list2[4]
In [77]:
adamsList
Out[77]:
['I', 'was', 'not', 'Adams']
In [78]:
adamsList is list2[4]
Out[78]:
True

Without a memory diagram, it's bit less obivous that adamsList is also an alias for myList[4]:

In [79]:
adamsList is myList[4]
Out[79]:
True

Now let's make myList[1] be an alias for myList[4]:

In [80]:
myList[1] = myList[4]

Finally, let's change adamsList[2] to be 'JQ':

In [81]:
adamsList[2] = 'JQ'

Can you predict the printed represenation of list2 now? Without memory diagrams, this can be very challenging to predict!

In [82]:
list2
Out[82]:
[98.6, ['I', 'was', 'JQ', 'Adams'], True, False, ['I', 'was', 'JQ', 'Adams']]

It turns out that the list adamsList has four other aliases in this example:

In [83]:
print('myList[1] is adamsList =>', myList[1] is adamsList)
print('myList[4] is adamsList =>', myList[4] is adamsList)
print('list2[1] is adamsList =>', list2[1] is adamsList)
print('list2[4] is adamsList =>', list2[4] is adamsList)
myList[1] is adamsList => True
myList[4] is adamsList => True
list2[1] is adamsList => True
list2[4] is adamsList => True

This can be very challenging to understand without the aid of the final memory diagram for this example:

This picture depicts a memory diagram for from the end of the second aliasing example. In this diagram, both `myList` and `list2` are aliases of the same 5-element list.  The slots in this list at indices 0, 2, and 3 hold the values 98.6, 23, and `True`, repectively. The slots at index 1 and 4 are aliases of the list `adamsList`, which is a list containing the four strings `'I'`, `'was'`, `'JQ'`, `'Adams'`.

9. Pop Quiz

What is the value of c[0] at the end of executing the following statements? Draw a memory diagram to justify your answer!

In [84]:
a = [15, 20]
b = [15, 20]
c = [10, a, b]
b[1] = 2*a[0]
c[1][0] = c[0]
c[0] = a[0] + c[1][1] + b[0] + c[2][1]
In [85]:
# Only check this after you've made a prediction!
c[0]
Out[85]:
75

Let's break down some of the code to see what is going on. Although a and b seem to have the same value, they occupy different addresses in the memory, so, they are not the same object.

In [86]:
a = [15, 20]
b = [15, 20]

print('id(a) =>', id(a))
print('id(b) =>', id(b))
print('a is b =>', a is b)
id(a) => 4407545544
id(b) => 4408070280
a is b => False

In the list c, c[1] is an alias for a and c[2] is an alias for b, but they are not aliases for each other:

In [87]:
c = [10, a, b]

print('c[1] is a =>', c[1] is a)
print('c[2] is b =>', c[2] is b)
print('c[1] is c[2] =>', c[1] is c[2])
c[1] is a => True
c[2] is b => True
c[1] is c[2] => False

Because of aliasing, changing a slot in b changes the same slot in c[2], but not in a or c[1]:

In [88]:
b[1] = 2*a[0]

print('b =>', b)
print('c[2] =>', c[2])
print('a =>', a)
print('c[1] =>', c[1])
b => [15, 30]
c[2] => [15, 30]
a => [15, 20]
c[1] => [15, 20]

Because c[1] is an alias for a, changing c[1][0] changes a[0] but does not affect b or c[2]:

In [89]:
c[1][0] = c[0]

print('a =>', a)
print('c[1] =>', c[1])
print('b =>', b)
print('c[2] =>', c[2])
a => [10, 20]
c[1] => [10, 20]
b => [15, 30]
c[2] => [15, 30]

Now we can determine the summands of a[0] + c[1][1] + b[0] + c[2][1]

In [90]:
print('a[0] =>', a[0])
print('c[1][1] =>', c[1][1])
print('b[0] =>', b[0])   
print('c[2][1] =>', c[2][1])

print('a[0] + c[1][1] + b[0] + c[2][1] =>', a[0] + c[1][1] + b[0] + c[2][1])

c[0] = a[0] + c[1][1] + b[0] + c[2][1]
print('c[0] =>', c[0])
a[0] => 10
c[1][1] => 20
b[0] => 15
c[2][1] => 30
a[0] + c[1][1] + b[0] + c[2][1] => 75
c[0] => 75

Did you get the correct answer?

Part 2 of Pop Quiz

In [91]:
# We'll change b to a[:]
a = [15, 20]
b = a[:]
c = [10, a, b]
b[1] = 2*a[0]
c[1][0] = c[0]
c[0] = a[0] + c[1][1] + b[0] + c[2][1]

print('id(a) =>', id(a))
print('id(b) =>', id(b))
print('a is b =>', a is b)

print(a)
print(b)
print(c)
id(a) => 4406640008
id(b) => 4406638152
a is b => False
[10, 20]
[15, 30]
[75, [10, 20], [15, 30]]

The operator [:] creates a new list that is a copy of the list in a, so the ids are still different for a and b. So the results for this second scenario are exactly the same as for the first scenario.

Part 3 of Pop Quiz

In [92]:
a = [15, 20]
b = a            # replace b = [15, 20] with b = a
c = [10, a, b]
b[1] = 2*a[0]
c[1][0] = c[0]
c[0] = a[0] + c[1][1] + b[0] + c[2][1]

print('id(a) =>', id(a))
print('id(b) =>', id(b))
print('a is b =>', a is b)

print(a)
print(b)
print(c)
id(a) => 4408043528
id(b) => 4408043528
a is b => True
[10, 30]
[10, 30]
[80, [10, 30], [10, 30]]

Since there is aliasing between a and b in this scenario, the final results are different from the previous two.

Question: What about strings? Are Strings mutable?

In [93]:
# We can use operations that work on sequences, like these:
state = 'Pennsylvania'
print(state[2])      
print(state[3:7])     
print('Penn' in state) 
n
nsyl
True

Try to change the string and see what happens:

In [94]:
state[0] = 's' 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-94-e64931428227> in <module>()
----> 1 state[0] = 's'

TypeError: 'str' object does not support item assignment

Can we append something onto the end of a string? Run the code below:

In [95]:
state.append('s')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-95-33d29d91ff8d> in <module>()
----> 1 state.append('s')

AttributeError: 'str' object has no attribute 'append'

Strings are NOT mutable, so we cannot perform mutations on them

10. Tuples

A tuple is an immutable sequence of values. It's written using parens rather than brackets.

In [96]:
# A homogeneous tuple of five integers
numTup = (5, 8, 7, 1, 3)

# A homogeneous tuple with 4 strings
stateTup = ('Kansas', 'Idaho', 'New Jersey', 'Louisiana')

# A pair is a tuple with two elements
pair = (7, 3)

# A tuple with one element must use a comma to avoid confusion with parenthesized expression
singleton = (7, )  

# A tuple with 0 values
emptyTup = ( )  

# A heterogeneous tuple with three elts 
heterogeneousTuple = (42, 'Hello', False)  

On tuples we can use any sequence operations that don't involve mutation:

In [97]:
len(stateTup)
Out[97]:
4
In [98]:
stateTup[2]
Out[98]:
'New Jersey'
In [99]:
stateTup[1:3]
Out[99]:
('Idaho', 'New Jersey')
In [100]:
'Missouri' in stateTup
Out[100]:
False
In [101]:
stateTup*2 + ('Arkansas',)
Out[101]:
('Kansas',
 'Idaho',
 'New Jersey',
 'Louisiana',
 'Kansas',
 'Idaho',
 'New Jersey',
 'Louisiana',
 'Arkansas')

However, any sequence operation that tries to change a tuple will fail

In [102]:
stateTup[0] = 'North Dakota'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-102-6bb73c78c447> in <module>()
----> 1 stateTup[0] = 'North Dakota'

TypeError: 'tuple' object does not support item assignment
In [103]:
stateTup.append('Maine')
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-103-3418c9620faf> in <module>()
----> 1 stateTup.append('Maine')

AttributeError: 'tuple' object has no attribute 'append'
In [104]:
stateTup.pop(3)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-104-8f6f12bf625e> in <module>()
----> 1 stateTup.pop(3)

AttributeError: 'tuple' object has no attribute 'pop'

11. Tuple Assignment

Consider an information tuple with three parts: (1) name of class (2) number of students (3) fulfills MM

In [105]:
classInfo = ('CS111', 24, True)

We can extract name parts of this tuple using three assignments:

In [106]:
name = classInfo[0]
numStudents = classInfo[1]
isMM = classInfo[2]
print('Name of Class:', name, '| Number of Students:', numStudents, '| Fulfills MM Requirement:', isMM)
Name of Class: CS111 | Number of Students: 24 | Fulfills MM Requirement: True

But it's simpler to extract all three parts in one so-called tuple assignment:

In [107]:
(name, numStudents, isMM) = classInfo
print('Name of Class:', name, '| Number of Students:', numStudents, '| Fulfills MM Requirement:', isMM)
Name of Class: CS111 | Number of Students: 24 | Fulfills MM Requirement: True

Also note that parens are optional in a tuple assignment, so this works, too:

In [108]:
name, numStudents, isMM = classInfo
print('Name of Class:', name, '| Number of Students:', numStudents, '| Fulfills MM Requirement:', isMM)
Name of Class: CS111 | Number of Students: 24 | Fulfills MM Requirement: True

12. Optional: Enumerations

An enumeration pairs an index with each element of a sequence.

In [109]:
enumerate('boston') # An enumeration is an abstract value
Out[109]:
<enumerate at 0x106ad3048>
In [110]:
list(enumerate('boston')) # Use list() to show the pairs in an enumeration
Out[110]:
[(0, 'b'), (1, 'o'), (2, 's'), (3, 't'), (4, 'o'), (5, 'n')]
In [111]:
list(enumerate([7, 2, 8, 5])) 
Out[111]:
[(0, 7), (1, 2), (2, 8), (3, 5)]

It's handy to loop over the (index, value) pairs in an enumeration.

In [112]:
for (i,char) in enumerate('boston'): # i and char are names for parts of each pair
    print(i, char)
0 b
1 o
2 s
3 t
4 o
5 n

The above shows that tuple assignment works for for loops, too!