Programming 1
Lecture 6: Notes

Some of today's topics are covered in these sections of Introducing Python:

Sets and dictionaries are especially important, so you should probably review their description in Introducing Python and/or in our Python Quick Reference.

Here are some additional notes.

String formatting

Recent versions of Python (3.6 or higher) include string interpolation, which lets you insert values inside a formatted string. For example:

color1 = 'blue'
color2 = 'green'

print(f'The sky is {color1} and the field is {color2}')

Write the character f immediately before a string to indicate that it is a formatted string. Interpolated values can be arbitrary expressions:

x = 14
y = 22

print(f'The sum is {x + y}.')

You may optionally specify a format code after each interpolated value to indicate how it should be rendered as a string. Some common format codes include

For example:

m = 127
e = 2.718281828459045

print(f'm in hex is {m:x}')   # prints 'm in hex is 7f'
print(f'e is {e:.4f}')        # prints 'e is 2.7183'

There are many more format codes which you can use to specify exact field widths, whether values should be left- or right-justified, and so on. See the Python library documentation for a complete description of these.

The import statement

For several weeks now we've been using the import statement to access modules in Python's standard library. For example:

import math
import random

x = math.sin(math.pi)
y = random.randint(1, 100)

It may become tiring to type prefixed function names such as "random.randint" over and over again. Fortunately, Python lets us import function names into the program's namespace directly, so that you can use them without these prefixes:

from random import randint

z = randint(1, 1000)   # no prefix!

You can also import multiple names at once:

from math import sin, cos, tan

Or even all names exported by a particular module:

from math import *

After the preceding declaration, you can use all math functions and constants without prefixing them:

x = sin(pi) + log(2 ** 5)

This style is convenient, but may make code harder to read, since without prefixes it may not be obvious which module each function comes from. This is to some degree a matter of style, but probably it is best to use prefixed names for all functions except those you call frequently.

Passing by value and reference

Consider this program:

def inc(i):
  i += 1
  print(i)

j = 7

inc(j)
print(j)

The program will print

8
7

Here is why. First the function inc receives the value i = 7. The statement "i += 1" sets i to 8, and inc writes this value.

Now control returns to the top-level code after the function definition. The value of j is still 7! That's because Python passes integer arguments by value: a function receives a local copy of the value that was passed. A function may modify its local copy, but that does not change the corresponding value in the caller, i.e. the code that called the function. And so the second number printed by this program is 7.

Now consider this variant:

def inc(l):
  l[0] += 1
  print(l[0])

a = [3, 5, 7]
inc(a)
print(a[0])

This program will print

4
4

This program behaves somewhat differently from the preceding one, because Python passes lists by reference. When the program calls inc(a), then as the function runs l and a are the same list. If we modify the list in the function, the change is visible in the caller.

Really this is similar to behavior that we see even without calling functions:

a = 4
b = a
a += 1   # does not change b

a = [4, 5, 6]
b = a
a[0] = 7  # change is visible in b[0]

(The description above is a bit imprecise. Strictly speaking, a Python variable does not hold a list directly, but instead holds a reference (pointer) to the list. And actually this pointer itself is passed by value when you call a function. If this makes sense to you, great; if not, don't worry about it at this point.)

Lists of lists

A list may hold other lists. For example, we may represent the matrix

 4   5   8
12   2  14
 5  11  16

using the nested list

m = [ [4, 5, 8], [12, 2, 14], [5, 11, 16] ]

Then

When we create a nested list representing a matrix, we need to be a bit careful because the sublists may be shared. For example:

>>> r = [0, 0, 0]
>>> m = [r, r, r]

Now m seems to hold a 3x3 matrix of zeros:

>>> m
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]

But now consider what happens if we assign

>>> m[0][0] = 8

Let's print the resulting matrix:

>>> m
[[8, 0, 0], [8, 0, 0], [8, 0, 0]]
>>>

All the values in the first column have changed! That's because all elements of m are references to the same list, namely the list r.

We might instead try to create a 3 x 3 matrix of zeroes like this:

m = 3 * [3 * [0]]   # Don't do this!

The effect will be the same, however: all three elements of m are the same list.

Since we want all the elements to be independent, we can build the matrix like this:

m = []

for i in range(3):
 m.append([0, 0, 0])

Now each row is a separate copy of [0, 0, 0]. If m[0][0] changes, that will not change other matrix elements.

Reading a matrix from text input

Suppose that we want to read a matrix from standard input, with one row per line:

 4   5   8
12   2  14
 5  11  16

We'd like to produce a nested list like this:

m = [ [4, 5, 8], [12, 2, 14], [5, 11, 16] ]

We can accomplish this via a double loop. We need to append each value to a row list, and append all the row lists to the matrix:

m = []
for line in sys.stdin:
  row = []
  for w in line.split():
    row.append(int(w))
  m.append(row)  

Returning multiple values

In the lecture we discussed tuples. (To review this topic, see the corresponding section in Introducing Python.)

You can conveniently use tuples to return multiple values from a function. For example:

def sumAndDifference(x, y):
 return x + y, x  y

Now we can call the function like this:

a, b = sumAndDifference(10, 5)

Now a will be 15 and b will be 5.

Or we can call the function and store the result in a single variable:

z = sumAndDifference(10, 5)

Now z hold be the tuple (15, 5).

If we attempt to assign the function's return value to more than two variables, we will get an error:

a, b, c = sumAndDifference(10, 5)   # error: not enough values to unpack