Programming 1, 2020-1
Lecture 4: Notes

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

And in these chapters of Introducing Python:

Here are some additional notes.

String formatting

Python includes f-strings, which are formatted strings that can contain interpolated values. 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.

structural and reference equality

Suppose we create a couple of lists as follows:

>>> x = [3, 5, 7, 9]
>>> y = x
>>> z = [3, 5, 7, 9]

Now the variables x and y refer to the same list. If we change x[0], then the change will be visible in y. However, z is a different list, and a change to x[0] will not be visible there:

>>> x[0] = 33
>>> y[0]
33
>>> z[0]
3

Python provides two different operators for testing equality. The first is the == operator:

>>> x == y
True
>>> x == z
True

This operator tests for structural equality. In other words, given two lists, it compares them element by element to see if they are equal. (It will even descend into sublists to compare elements there as well.)

The second equality operator is the is operator:

>>> x is y
True
>>> x is z
False

This operator tests for reference equality. In other words, it returns true only if its arguments actually refer to the same object.

You may want to use each of these operators in various situations. Note that is returns instantly, whereas == traverses lists in their entirety and so may be slower if lists are large.

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]

local and global variables

Consider this Python program:

x = 7

def abc(a):
    i = a + x
    return i

def ha():
    i = 4
    print(abc(2))
    print(x + i)

The variable x declared at the top is a global variable. Its value is visible everywhere: both inside the function abc(), and also in the top-level code at the end of the program.

The variables i declared inside abc() and ha() are local variables. They are different variables: when the line "i = a + x" executes inside abc(), that does not change the value of i in ha().

Now consider this variation of the program above:

x = 7
i = 4

def abc(a):
    i = a + x
    return i

print(abc(2))
print(x + i)

In this version, the variables x and i declared at the top are both global. abc() declares its own local i. This is not the same as the global i. In particular, when abc() runs "i = a + x", this does not change the value of the global. The local i is said to shadow the global i inside the function body.

Local variables are a fundamental feature of every modern programming language. Because a local variable's scope (the area of the program where it is visible) is small, it is easy to understand how the variable will behave. I recommend making variables local whenever possible.

In the program above, what if we want the function abc() to use the global i, rather than making a new local variable? We can achieve this by declaring i as global:

x = 7
i = 4

def abc(a):
    global i
    i = a + x
    return i

print(abc(2))
print(x + i)

Now the line "i = a + x" will update the global i.

Notice that in all of the programs above, abc() is able to read the global x without declaring it as global. But if a function wants to write to a global variable, it must declare the variable as global.

To be more precise, here is how Python determines whether each variable in a function is local or global:

Tutorial

We solved this exercise in the tutorial:

6. Lottery Ticket

A country is holding a lottery in which 5 winning numbers are chosen at random from the range 1..25. No two of these numbers will be the same. For example, the winning numbers could be 2 – 4 – 9 – 18 – 22, or 5 – 6 – 11 – 15 – 24.

Write a program that randomly selects 5 winning numbers and prints them on a single line in ascending order.

Here's the solution we wrote:

import random

l = 26 * [ False ]   # l[0], l[1] .... l[25]

count = 0  # how many numbers we have already
while count < 5:
    i = random.randint(1, 25)
    if not l[i]:
        l[i] = True
        count += 1

s = ''
for i in range(1, 26):
    if l[i]:
        s = s + str(i) + ' '
print(s)

Here's another possible solution, which you may wish to compare with the preceding one:

import random

nums = list(range(1, 26))   # [1, 2, ... 25 ]
selected = []

for x in range(5):
    k = random.randint(0, len(nums) - 1)
    selected.append(nums[k])
    del nums[k]

s = ''
for i in sorted(selected):
    s = s + str(i) + ' '

print(s)