These notes follow the official python tutorial pretty closely: http://docs.python.org/3/tutorial/


In [ ]:
from __future__ import print_function

Lists

Lists group together data. Many languages have arrays (we'll look at those in a bit in python). But unlike arrays in most languages, lists can hold data of all different types -- they don't need to be homogeneos. The data can be a mix of integers, floating point or complex #s, strings, or other objects (including other lists).

A list is defined using square brackets:


In [10]:
a = [1, 2.0, "my list", 4]

In [11]:
print(a)


[1, 2.0, 'my list', 4]

We can index a list to get a single element -- remember that python starts counting at 0:


In [12]:
print(a[2])


my list

Like with strings, mathematical operators are defined on lists:


In [13]:
print(a*2)


[1, 2.0, 'my list', 4, 1, 2.0, 'my list', 4]

The len() function returns the length of a list


In [14]:
print(len(a))


4

Unlike strings, lists are mutable -- you can change elements in a list easily


In [15]:
a[1] = -2.0
a


Out[15]:
[1, -2.0, 'my list', 4]

In [16]:
a[0:1] = [-1, -2.1]   # this will put two items in the spot where 1 existed before
a


Out[16]:
[-1, -2.1, -2.0, 'my list', 4]

Note that lists can even contain other lists:


In [17]:
a[1] = ["other list", 3]
a


Out[17]:
[-1, ['other list', 3], -2.0, 'my list', 4]

Just like everything else in python, a list is an object that is the instance of a class. Classes have methods (functions) that know how to operate on an object of that class.

There are lots of methods that work on lists. Two of the most useful are append, to add to the end of a list, and pop, to remove the last element:


In [18]:
a.append(6)
a


Out[18]:
[-1, ['other list', 3], -2.0, 'my list', 4, 6]

In [19]:
a.pop()


Out[19]:
6

In [20]:
a


Out[20]:
[-1, ['other list', 3], -2.0, 'my list', 4]

Quick Exercise:

An operation we'll see a lot is to begin with an empty list and add elements to it. An empty list is created as:

a = []
  • Create an empty list
  • Append the integers 1 through 10 to it.
  • Now pop them out of the list one by one.


In [24]:
a = []
a.append(1)
a.append(2)
a.append(3)
a.append(4)
a.append(5)
a


Out[24]:
[1, 2, 3, 4, 5]

In [25]:
a.pop()


Out[25]:
5

In [26]:
a.pop()


Out[26]:
4

In [27]:
a.pop()


Out[27]:
3

In [28]:
a.pop()


Out[28]:
2

In [29]:
a.pop()


Out[29]:
1

In [30]:
a.pop()


---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-30-353a8f813834> in <module>()
----> 1 a.pop()

IndexError: pop from empty list

In [ ]:

copying may seem a little counterintuitive at first. The best way to think about this is that your list lives in memory somewhere and when you do

a = [1, 2, 3, 4]

then the variable a is set to point to that location in memory, so it refers to the list.

If we then do

b = a

then b will also point to that same location in memory -- the exact same list object.

Since these are both pointing to the same location in memory, if we change the list through a, the change is reflected in b as well:


In [ ]:
a = [1, 2, 3, 4]
b = a  # both a and b refer to the same list object in memory
print(a)
a[0] = "changed"
print(b)

if you want to create a new object in memory that is a copy of another, then you can either index the list, using : to get all the elements, or use the list() function:


In [ ]:
c = list(a)   # you can also do c = a[:], which basically slices the entire list
a[1] = "two"
print(a)
print(c)

Things get a little complicated when a list contains another mutable object, like another list. Then the copy we looked at above is only a shallow copy. Look at this example—the list within the list here is still the same object in memory for our two copies:


In [ ]:
f = [1, [2, 3], 4]
print(f)

In [ ]:
g = list(f)
print(g)

Now we are going to change an element of that list [2, 3] inside of our main list. We need to index f once to get that list, and then a second time to index that list:


In [ ]:
f[1][0] = "a"
print(f)
print(g)

Note that the change occured in both—since that inner list is shared in memory between the two. Note that we can still change one of the other values without it being reflected in the other list—this was made distinct by our shallow copy:


In [ ]:
f[0] = -1
print(g)
print(f)

Note: this is what is referred to as a shallow copy. If the original list had any special objects in it (like another list), then the new copy and the old copy will still point to that same object. There is a deep copy method when you really want everything to be unique in memory.

When in doubt, use the id() function to figure out where in memory an object lies (you shouldn't worry about the what value of the numbers you get from id mean, but just whether they are the same as those for another object)


In [ ]:
print(id(a), id(b), id(c))

There are lots of other methods that work on lists (remember, ask for help)


In [ ]:
my_list = [10, -1, 5, 24, 2, 9]
my_list.sort()
print(my_list)

In [ ]:
print(my_list.count(-1))
my_list

In [ ]:
help(a.insert)

In [ ]:
a.insert(3, "my inserted element")

In [ ]:
a

joining two lists is simple. Like with strings, the + operator concatenates:


In [ ]:
b = [1, 2, 3]
c = [4, 5, 6]
d = b + c
print(d)

Dictionaries

A dictionary stores data as a key:value pair. Unlike a list where you have a particular order, the keys in a dictionary allow you to access information anywhere easily:


In [ ]:
my_dict = {"key1":1, "key2":2, "key3":3}

In [ ]:
print(my_dict["key1"])

you can add a new key:pair easily, and it can be of any type


In [ ]:
my_dict["newkey"] = "new"
print(my_dict)

Note that a dictionary is unordered.

You can also easily get the list of keys that are defined in a dictionary


In [ ]:
keys = list(my_dict.keys())
print(keys)

and check easily whether a key exists in the dictionary using the in operator


In [ ]:
print("key1" in keys)
print("invalidKey" in keys)

List Comprehensions

list comprehensions provide a compact way to initialize lists. Some examples from the tutorial


In [ ]:
squares = [x**2 for x in range(10)]

In [ ]:
squares

here we use another python type, the tuple, to combine numbers from two lists into a pair


In [ ]:
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]

Quick Exercise:

Use a list comprehension to create a new list from squares containing only the even numbers. It might be helpful to use the modulus operator, %



In [ ]:

Tuples

tuples are immutable -- they cannot be changed, but they are useful for organizing data in some situations. We use () to indicate a tuple:


In [ ]:
a = (1, 2, 3, 4)

In [ ]:
print(a)

We can unpack a tuple:


In [ ]:
w, x, y, z = a

In [ ]:
print(w)

In [ ]:
print(w, x, y, z)

Since a tuple is immutable, we cannot change an element:


In [ ]:
a[0] = 2

But we can turn it into a list, and then we can change it


In [ ]:
z = list(a)

In [ ]:
z[0] = "new"

In [ ]:
print(z)

Control Flow

To write a program, we need the ability to iterate and take action based on the values of a variable. This includes if-tests and loops.

Python uses whitespace to denote a block of code.

While loop

A simple while loop—notice the indentation to denote the block that is part of the loop.

Here we also use the compact += operator: n += 1 is the same as n = n + 1


In [ ]:
n = 0
while n < 10:
    print(n)
    n += 1

This was a very simple example. But often we'll use the range() function in this situation. Note that range() can take a stride.


In [ ]:
for n in range(2, 10, 2):
    print(n)

In [ ]:
print(list(range(10)))

if statements

if allows for branching. python does not have a select/case statement like some other languages, but if, elif, and else can reproduce any branching functionality you might need.


In [ ]:
x = 0

if x < 0:
    print("negative")
elif x == 0:
    print("zero")
else:
    print("positive")

Iterating over elements

it's easy to loop over items in a list or any iterable object. The in operator is the key here.


In [ ]:
alist = [1, 2.0, "three", 4]
for a in alist:
    print(a)

In [ ]:
for c in "this is a string":
    print(c)

We can combine loops and if-tests to do more complex logic, like break out of the loop when you find what you're looking for


In [ ]:
n = 0
for a in alist:
    if a == "three":
        break
    else:
        n += 1

print(n)

(for that example, however, there is a simpler way)


In [ ]:
print(alist.index("three"))

for dictionaries, you can also loop over the elements


In [ ]:
my_dict = {"key1":1, "key2":2, "key3":3}

for k, v in my_dict.items():
    print("key = {}, value = {}".format(k, v))    # notice how we do the formatting here

In [ ]:
for k in sorted(my_dict):
    print(k, my_dict[k])

sometimes we want to loop over a list element and know its index -- enumerate() helps here:


In [ ]:
for n, a in enumerate(alist):
    print(n, a)

Quick Exercise:

zip() allows us to loop over two iterables at the same time. Consider the following two lists:

a = [1, 2, 3, 4, 5, 6, 7, 8]
b = ["a", "b", "c", "d", "e", "f", "g", "h"]

zip(a, b) will act like a list with each element a tuple with one item from a and the corresponding element from b.

Try looping over these lists together (using zip()) and print the corresponding elements from each list together on a single line.



In [ ]:

Quick Exercise:

The .split() function on a string can split it into words (separating on spaces).

Using .split(), loop over the words in the string

a = "The quick brown fox jumped over the lazy dog"

and print one word per line



In [ ]: