An Introduction To Programming and Computer Science With Pyt
An Introduction To Programming and Computer Science With Pyt
CLAYTON CAFIERO
An Introduction to
Programming and Computer Science
with Python
Clayton Cafiero
The University of Vermont
This book is for free use under either the GNU Free Documentation License or
the Creative Commons Attribution-ShareAlike 3.0 United States License. Take
your pick.
• http://www.gnu.org/copyleft/fdl.html
• http://creativecommons.org/licenses/by-sa/3.0/us/
Book style has been adapted from the Memoir class for TEX, copyright © 2001–
2011 Peter R. Wilson, 2011–2022 Lars Madsen, and is thus excluded from the
above licence.
Images from Matplotlib.org in Chapter 15 are excluded from the license for
this material. They are subject to Matplotlib’s license at https://matplotlib.o
rg/stable/users/project/license.html. Photo of Edsger Dijkstra by Hamilton
Richards, University Texas at Austin, available under a Creative Commons CC
BY-SA 3.0 license: https://creativecommons.org/licenses/by-sa/3.0/.
No generative AI was used in writing this book.
Manuscript prepared by the author with Quarto, Pandoc, and XƎLATEX.
Illustrations, diagrams, and cover artwork by the author, except for the graph
in Chapter 17, Exercise 2, which is by Harry Sharman.
Version: 0.1.8b (beta)
ISBN: 979-8-9887092-0-6
Library of Congress Control Number: 2023912320
First edition
10 9 8 7 6 5 4 3 2
Printed in the United States of America
For the Bug and the Bull
Table of contents
Table of contents i
Preface v
Acknowledgements ix
1 Introduction 1
5 Functions 75
5.1 Introduction to functions . . . . . . . . . . . . . . . . . . 76
i
ii Table of contents
6 Style 97
6.1 The importance of style . . . . . . . . . . . . . . . . . . . 97
6.2 PEP 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.3 Whitespace . . . . . . . . . . . . . . . . . . . . . . . . . . 98
6.4 Names (identifiers) . . . . . . . . . . . . . . . . . . . . . . 100
6.5 Line length . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.6 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
6.7 Comments in code . . . . . . . . . . . . . . . . . . . . . . 102
6.8 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
10 Sequences 183
10.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
10.2 Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
10.3 Mutability and immutability . . . . . . . . . . . . . . . . 194
10.4 Subscripts are indices . . . . . . . . . . . . . . . . . . . . 198
10.5 Concatenating lists and tuples . . . . . . . . . . . . . . . 199
10.6 Copying lists . . . . . . . . . . . . . . . . . . . . . . . . . 200
10.7 Finding an element within a sequence . . . . . . . . . . . 201
10.8 Sequence unpacking . . . . . . . . . . . . . . . . . . . . . 203
10.9 Strings are sequences . . . . . . . . . . . . . . . . . . . . . 205
10.10 Sequences: a quick reference guide . . . . . . . . . . . . . 206
10.11 Slicing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 207
10.12 Passing mutables to functions . . . . . . . . . . . . . . . . 210
10.13 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 212
10.14 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213
16 Dictionaries 311
16.1 Introduction to dictionaries . . . . . . . . . . . . . . . . . 311
16.2 Iterating over dictionaries . . . . . . . . . . . . . . . . . . 316
16.3 Deleting dictionary keys . . . . . . . . . . . . . . . . . . . 318
16.4 Hashables . . . . . . . . . . . . . . . . . . . . . . . . . . . 319
16.5 Counting letters in a string . . . . . . . . . . . . . . . . . 321
16.6 Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . 322
16.7 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323
17 Graphs 325
17.1 Introduction to graphs . . . . . . . . . . . . . . . . . . . . 325
17.2 Searching a graph: breadth-first search . . . . . . . . . . . 327
17.3 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
Appendices 333
A Glossary 333
This book has been written for use in University of Vermont’s CS1210
Introduction to Programming (formerly CS021). This is a semester long
course which covers much of the basics of programming, and an intro-
duction to some fundamental concepts in computer science. Not being
happy with any of the available textbooks, I endeavored to write my own.
Drafting began in August 2022, essentially writing a chapter a week over
the course of the semester, delivered to students via UVM’s learning
management system. The text was revised, edited, and expanded in the
following semester.
UVM’s CS1210 carries “QR” (quantitative reasoning) and “QD”
(quantitative and data literacy) designations. Accordingly, there’s some
mathematics included:
v
vi Preface
Contact
Clayton Cafiero
The University of Vermont
College of Engineering and Mathematical Sciences
Department of Computer Science
Innovation E309
82 University Place
Burlington, VT 05405-0125 (USA)
cbcafier@uvm.edu
https://www.uvm.edu/~cbcafier
To the student
vii
Acknowledgements
ix
Chapter 1
Introduction
Computer science is a science of abstraction—creating
the right model for a problem and devising the appro-
priate mechanizable techniques to solve it.
–Alfred V. Aho
1
2 Introduction
Python interpreter. This chapter also introduces the two modes of using
Python. The interactive mode allows the user to interact with the Python
interpreter using the Python shell. Python statements and expressions
are entered one at a time, and the interpreter evaluates or executes the
code entered by the user. This is an essential tool for experimentation
and learning the details of various language features. Script mode allows
the user to write, save, and execute Python programs. This is convenient
since in this mode we can save our work, and run it multiple times
without having to type it again and again at the Python shell.
This chapter also includes a brief introduction to binary numbers and
binary arithmetic.
Functions
Functions are the single most important concept a beginning programmer
can acquire. Functional decomposition is a crucial requirement of writing
reliable, robust, correct code.
This chapter explains why we use functions, how functions are defined,
how functions are called, and how values are returned. We’ve tried to
keep this “non-technical” and so there’s no discussion of a call stack,
though there is discussion of scope.
Because beginning programmers often introduce side effects into func-
tions where they are undesirable or unnecessary, this chapter makes clear
the distinction between pure functions (those without side effects) and
impure functions (those with side effects, including mutating mutable
objects).
Because the math module is so widely used and includes many useful
functions, we introduce the math module in this chapter. In this way, we
also reinforce the idea of information hiding and good functional design.
Do we need to know how the math module implements its sqrt() function?
Of course not. Should we have to know how a function is implemented
3
in order to use it? Apart from knowing what constitutes a valid input
and what it returns as an output, no, we do not!
Style
Our goal here is to encourage the writing of idiomatic Python. Accord-
ingly, we address the high points of PEP 8—the de facto style guide for
Python—and provide examples of good and bad style.
Students don’t always understand how important style is for the read-
ability of one’s code. By following style guidelines we can reduce the
cognitive load that’s required to read code, thereby making it easier to
reason about and understand our code.
Branching
Branching is a programming language’s way of handling conditional ex-
ecution of code. In this chapter, we cover conditions (Boolean expres-
sions) which evaluate to a true or false (or a value which is “truthy”
or “falsey”—like true or like false). Python uses these conditions to de-
termine whether a block of code should be executed. In many cases we
have multiple branches—multiple paths of execution that might be taken.
These are implemented with if, elif (a portmanteau of “else if”), and
often else.
One common confusion that beginners face is understanding which
branch is executed in an if/elif/else structure, and hopefully the chapter
makes this clear.
Also covered are nested if statements, and two ways of visually rep-
resenting branching (each appropriate to different use cases)—decision
trees and flow charts.
Sequences
Sequences—lists, tuples, and strings—are presented in this chapter. It
makes sense to present these before presenting loops for two reasons.
First, sequences are iterable, and as such are used in for loops, and with-
out a clear understanding of what constitutes an iterable, understanding
such loops may present challenges. Second, we often do work within a
loop which might involve constructing or filtering a list of objects.
Common features of sequences—for example, they are all indexed,
support indexed reads, and are iterable—are highlighted throughout the
chapter.
As this chapter introduces our first mutable type, the Python list, we
present the concepts of mutability and immutability in this chapter.
Loops
Loops allow for repetitive work or calculation. In this chapter we present
the two kinds of loop supported by Python—while loops and for loops.
At this point, students have seen iterables (in the form of sequences)
and Boolean expressions, which are a necessary foundation for a proper
presentation of loops.
Also, this chapter introduces two new types—range and enumerate—
and their corresponding constructors. Presentation of range entails dis-
cussion of arithmetic sequences, and presentation of enumerate works
nicely with tuple unpacking (or more generally, sequence unpacking),
and so these are presented first in this chapter.
This chapter also provides a brief introduction to stacks and queues,
which are trivially implemented in Python using list as an underlying
data structure.
I’ve intentionally excluded treatment of comprehensions since begin-
ners have difficulty reading and writing comprehensions without a prior,
solid foundation in for loops.
agers were introduced with Python 2.5 in 2006, and are a much preferred
idiom (as compared to using try/finally). Accordingly, all file I/O demon-
strations make use of context managers created with the Python keyword
with.
Because so much data is in CSV format (or can be exported to this
format easily), we introduce the csv module in this chapter. Using the
csv module reduces some of the complexity we face when reading data
from a file, since we don’t have to parse it ourselves.
Exception handling
In this chapter, we present simple exception handling (using try/except,
but not finally), and explain that some exceptions should not be han-
dled since in doing so, we can hide programming defects which should
be corrected. We also demonstrate the use of exception handling in in-
put validation. When you reach this chapter, you’ll already have seen
while loops for input validation, so the addition of exception handling
represents only an incremental increase in complexity in this context.
Dictionaries
Dictionaries are the last new type we present in the text. Dictionaries
store information using a key/value model—we look up values in a dic-
tionary by their keys. Like sequences, dictionaries are iterable, but since
they have keys rather than indices, this works a little differently. We’ll
see three different ways to iterate over a dictionary.
We’ll also learn about hashability in the context of dictionary keys.
Graphs
Since graphs are so commonplace in computer science, it seems appro-
priate to include a basic introduction to graphs in this text. Plus, graphs
are really fun!
A graph is a collection of vertices (also called nodes) and edges, which
connect the vertices of the graph. The concrete example of a highway map
is used, and an algorithm for breadth-first search (BFS) is demonstrated.
Since queues were introduced in chapter 11, the conceptual leap here—
using a queue in the BFS algorithm—shouldn’t be too great.
6 Introduction
print("Hello, World!")
>>> 1 + 2
3
>>> import math
>>> math.sqrt(36)
6
2
Except in the case of doctests, which are not presented in this text.
7
Other conventions
When referring to functions, whether built-in, from some imported mod-
ule, or otherwise, without any other context or specific problem instance,
we write function identifiers along with parentheses (as a visual indica-
tor that we’re speaking of a function) but without formal parameters.
Example: “The range() function accepts one, two, or three arguments.”
This should not be read as suggesting that range() takes no arguments.
if __name__ == '__main__':
# the rest of your code here
Origin of Python
Python has been around a long time, with the first release appearing
in 1991 (four years before Java). It was invented by Guido van Rossum,
who is now officially Python’s benevolent dictator for life (BDFL).
8 Introduction
Python gets its name from the British comedy troupe Monty Python’s
Flying Circus (Guido is a fan).
Nowadays, Python is one of the most widely used programming lan-
guages on the planet and is supported by an immense ecosystem and
thriving community. See: https://python.org/ for more.
Python version
As this book is written, the current version of Python is 3.11.4. However,
no new language features introduced since version 3.6 are presented in
this book (as most are not appropriate or even useful for beginners).
This book does cover f-strings, which were introduced in version 3.6.
Accordingly, if you have Python version 3.6–3.11, you should be able to
follow along with all code samples and exercises.
Pros
• Definitive and up-to-date
• Documentation for different versions clearly specified
• Thorough and accurate
• Includes references for all standard libraries
• Available in multiple languages
• Includes a comprehensive tutorial for Python beginners
• Coding examples (where given) conform to good Python style (PEP
8)
Cons
• Can be lengthy or technical—not always ideal for beginners
• Don’t always appear at top of search engine results.
python.org
Official Python documentation, tutorials, and other resources are hosted
at https://python.org.
9
• Documentation: https://docs.python.org/3/
• Tutorial: https://docs.python.org/3/tutorial/
• Beginner’s Guide: https://wiki.python.org/moin/BeginnersGuide
Á Warning
Our objective for this chapter is to lay the foundations for the rest of
the course. If you’ve done any programming before, some of this may
seem familiar, but read carefully nonetheless. If you haven’t done any
programming before that’s OK.
Learning objectives
• You will learn how to interact with the Python interpreter using
the Python shell.
• You will learn the difference between interactive mode (in the shell)
and script mode (writing, saving, and running programs).
• You will learn a little about computers, how they are structured,
and that they use binary code.
• You will understand why we wish to write code in something other
than just zeros and ones, and you’ll learn a little about how Python
translates high-level code (written by you, the programmer) into
binary instructions that a computer can execute.
• You will write, save, and run your first Python program—an or-
dered collection of statements and expressions.
Terms introduced
• binary code
• bytecode
• compilation vs interpretation
• compiler
• console
• integrated development environment (IDE)
• interactive mode
• low-level vs high-level programming language
• Python interpreter / shell
• read-evaluate-print loop (REPL)
• semantics
• script mode
11
12 Programming and the Python Shell
• syntax
• terminal
This is unintelligible. It’s bad enough to try to read it, and it would be
even worse if we had to write our computer programs in this fashion.
Computers don’t speak human language, and humans don’t speak
computer language. That’s a problem. The solution is programming lan-
guages.
Programming languages allow us, as humans, to write instructions
in a form we can understand and reason about, and then have these
instructions converted into a form that a computer can read and execute.
There is a tremendous variety of programming languages. Some lan-
guages are low-level, like assembly language, where there’s roughly a
one-to-one correspondence between machine instructions and assembly
language instructions. Here’s a “Hello World!” program in assembly lan-
guage (for ARM64 architecture):1
.equ STDOUT, 1
.equ SVC_WRITE, 64
.equ SVC_EXIT, 93
.text
.global _start
_start:
stp x29, x30, [sp, -16]!
mov x0, #STDOUT
ldr x1, =msg
mov x2, 13
1
Assembly language code sample from Rosetta Code: https://www.rosettacode.
org/wiki/Hello_world
Why learn a programming language? 13
Now, while this is a lot better than a string of zeros and ones, it’s not
so easy to read, write, and reason about code in assembly language.
Fortunately, we have high-level languages. Here’s the same program
in C++:
#include <iostream>
int main () {
std::cout << "Hello World!" << std::endl;
}
print('Hello World!')
print('Hello World!')
You may run this program from the terminal (command prompt), thus:
$ python hello_world.py
Hello World!
When we run this program, Python first reads the source code, then
produces the intermediate bytecode, then executes each instruction in
the bytecode.
So you see, there’s a lot going on behind the scenes when we run a
Python program.2 However, this allows us to write programs in a high-
level language that we as humans can understand.
Supplemental reading
• Whetting Your Appetite, from The (Official) Python Tutorial.3
>>> 1
1
2
Actually, there’s quite a bit more going on behind the scenes, but this should
suffice for our purposes. If you’re curious and wish to learn more, ask!
3
https://docs.python.org/release/3.10.4/tutorial/appetite.html
The Python shell 17
>>> 1 + 2
3
Python understands arithmetic and when the operands are numbers (in-
tegers or floating-point) then + works just like you’d expect. So here we
have a simple expression—a syntactically valid sequence of symbols that
evaluates to a value. What does this expression evaluate to? 3 of course!
We refer to the + operator as a binary infix operator, since it takes
two operands (hence, “binary”) and the operand is placed between the
operands (hence, “infix”).
Here’s another familiar binary infix operator: -. You already know
what this does.
>>> 17 - 5
12
Yup. Just as you’d expect. The Python shell evaluates the expression 17
- 5 and returns the result: 12.
REPL
This process—of entering an expression and having Python evaluate it
and display the result—is called REPL which is an acronym for read-
evaluate-print loop. Many languages have REPLs, and obviously, Python
does too. REPLs were invented (back in the early 1960s) to provide an
environment for exploratory programming. This is facilitated by allowing
the programmer to see the result of each portion of code they enter.
Accordingly, I encourage you to experiment with the Python shell. Do
18 Programming and the Python Shell
some tinkering and see the results. You can learn a lot by working this
way.
>>> exit()
print("Hello World!")
That’s it!
If we want to run a program in script mode we must write it and save
it. Let’s do that.
In your editor or IDE open a new file, and enter this one line of code
(above). Save the file as hello_world.py.
Now you can run your program. If you’re using an IDE, you can run
the file within your IDE. You can also run the file from the command
line, e.g.,
$ python hello_world.py
where $ is the command line prompt (this will vary from system to
system). The $ isn’t something you type, it’s just meant to indicate a
command prompt (like >>> in the Python shell). When you run this
program it should print:
Syntax and semantics 19
Hello World!
Next steps
The basic steps above will be similar for each new program you write. Of
course, as we progress, programs will become more challenging, and it’s
likely you may need to test a program by running it multiple times as
you make changes before you get it right. That’s to be expected. But now
you’ve learned the basic steps to create a new file, write some Python
code, and run your program.
Syntax
In a (natural) language course—say Spanish, Chinese, or Latin—you’d
learn about certain rules of syntax, that is, how we arrange words and
choose the correct forms of words to produce a valid sentence or utter-
ance. For example, in English,
>>> 2 3
is not syntactically valid. If we were to try this using the Python shell,
the Python interpreter would complain.
4
“My hovercraft is full of eels” originates in a famous sketch by Monty Python’s
Flying Circus.
20 Programming and the Python Shell
>>> 2 3
File "<stdin>", line 1
2 3
^^^
SyntaxError: invalid syntax. Perhaps you forgot a comma?
>>> = 5
File "<stdin>", line 1
= 5
^
SyntaxError: invalid syntax
Python makes it clear when we have syntax errors in our code. Usually
it can point to the exact position within a line where such an error occurs.
Sometimes, it can even provide suggestions, e.g. “Perhaps you forgot a
comma?”
Semantics
On the other hand, semantics is about meaning. In English we may say
Does this mean that someone has cooked a turkey and that it is ready to
be eaten? Or does this mean that there’s a hungry turkey who is ready
to be fed? This kind of ambiguity is quite common in natural languages.
Not so with programming languages. If we’ve produced a syntactically
valid statement or expression, it has only one “interpretation.” There is
no ambiguity in programming.
Here’s another famous example, devised by the linguist Noam Chom-
sky:5
and now this prints 20.0 which is correct. Now our program has the
semantics we intend for it.
1 × 29 = 1000000000
1 × 28 = 0100000000
1 × 27 = 0010000000
1 × 26 = 0001000000
0 × 25 = 0000000000
0 × 24 = 0000000000
1 × 23 = 0000001000
1 × 22 = 0000000100
1 × 21 = 0000000010
1 × 20 = 0000000001
and that all adds up to 1111001111. To verify, let’s represent these values
in decimal format and check our arithmetic.
1 × 29 = 512
1 × 28 = 256
1 × 27 = 128
1 × 26 = 064
0 × 25 = 000
0 × 24 = 000
1 × 23 = 008
1 × 22 = 004
1 × 21 = 002
1 × 20 = 001
Indeed, this adds to 975.
Where in the decimal system we have the ones place, the tens place,
the hundreds place, and so on, in the binary system we have the ones
place, the twos place, the fours place, and so on.
How would we write, in binary, the decimal number 3? 11. That’s one
two, and one one.
How about the decimal number 10? 1010. That’s one eight, zero fours,
one two, and zero ones.
How about the decimal number 13? 1101. That’s one eight, one four,
zero twos, and one one.
Binary arithmetic
Once you get the hang of it, binary arithmetic is straightforward. Here’s
the most basic example: adding 1 and 1.
1
+ 1
1 0
Exercises 25
In the ones column we add one plus one, that’s two—binary 10—so we
write 0, carry 1 into the twos column, and then write 1 in the twos
column, and we’re done.
Now let’s add 1011 (decimal 11) and 11 (decimal 3).
1 0 1 1
+ 1 1
1 1 1 0
In the ones column we add one plus one, that’s two—binary 10—so we
write 0 and carry 1 into the twos column. Then in the twos column we
add one (carried) plus one, plus one, that’s three—binary 11—so we
write 1 and carry 1 into the fours column. In the fours column we add
one (carried) plus zero, so we write 1, and we have nothing to carry. In
the eights column we have only the single eight, so we write that, and
we’re done. To verify (in decimal):
1 × 23 + 1 × 2 2 + 1 × 2 1 + 0 × 2 0 = 1 × 8 + 1 × 4 + 1 × 2 + 0 × 1
= 14
2.7 Exercises
Exercise 01
Write a line of Python code that prints your name to the console.
Exercise 02
Multiple choice: Python is a(n) ________ programming language.
a. compiled
b. assembly
c. interpreted
d. binary
Exercise 03
True or false? Code that you write in the Python shell is saved.
Exercise 04
How do you exit the Python shell?
Exercise 05
Python can operate in two different modes. What are these modes and
how do they differ?
26 Programming and the Python Shell
Exercise 06
The following is an example of what kind of code?
Exercise 07
Calculate the following sums in binary:
a. 10 + 1
b. 100 + 11
c. 11 + 11
d. 1011 + 10
After you’ve worked these out in binary, convert to decimal form and
check your arithmetic.
Exercise 08 (challenge!)
Try binary subtraction. What is 11011 - 1110? After calculating in binary,
convert to decimal and check your answer.
Chapter 3
Learning objectives
• You will learn about many commonly used types in Python.
• You will understand why we have different types.
• You will be able to write literals of various types.
• You will learn different ways to write string literals which include
various quotation marks within them.
• You will learn about representation error as it applies to numeric
types (especially floating-point values).
Terms introduced
• dynamic typing
• escape sequence
• empty string, empty tuple, and empty list
• heterogeneous
• literal
• representation error
• static typing
• “strong” vs “weak” typing
• type (including int, float, str, list, tuple, dict, etc.)
• type inference
• Unicode
27
28 Types and literals
What’s a literal?
A literal is simply fixed values of a given type. For example, 1 is a literal.
It means, literally, the integer 1. Other examples of literals follow.
int
The int type represents integers, that is, whole numbers, positive or
negative, and zero. Examples of int literals: 1, 42, -99, 0, 10000000, etc.
For readability, we can write integer literals with underscores in place
of thousands separators. For example, 1_000_000 is rather easier to read
than 1000000, and both have the same value.
float
Objects of the float type represent floating-point numbers, that is, num-
bers with decimal (radix) points. These approximate real numbers (to
varying degrees; see the section on representation error). Examples of
float literals: 1.0, 3.1415, -25.1, etc.
str
A string is an ordered sequence of characters. Each word on this page is
a string. So are "abc123" and "@&)z)$"—the symbols of a string needn’t
be alphabetic. In Python, objects of the str (string) type hold zero or
more symbols in an ordered sequence. Strings must be delimited to dis-
tinguish them from variable names and other identifiers which we’ll see
later. Strings may be delimited with single quotation marks, double quo-
tation marks, or “triple quotes.” Examples of str literals: "abc", "123",
"vegetable", "My hovercraft is full of eels.", """What nonsense is
this?""", etc.
Single and double quotation marks are equivalent when delimiting
strings, but you must be consistent in their use—starting and ending
delimiters must be the same. "foo" and 'foo' are both valid string literals;
"foo' and 'foo" are not.
>>> "foo'
File "<stdin>", line 1
"foo'
^
SyntaxError: unterminated string literal (detected at line 1)
30 Types and literals
bool
bool type is used for two special values in Python: True and False. bool
is short for “Boolean”, named after George Boole (1815–1864), a largely
self-taught logician and mathematician, who devised Boolean logic—a
cornerstone of modern logic and computer science (though computers
did not yet exist in Boole’s day).
There are only two literals of type bool: True and False. Notice that
these are not strings, but instead are special literals of this type (so there
aren’t any quotation marks, and capitalization is significant).1
NoneType
NoneType is a special type in Python to represent the absence of a value.
This may seem a little odd, but this comes up quite often in programming.
There is exactly one literal of this type: None (and indeed there is exactly
one instance of this type).
Like True and False, None is not a string, but rather a special literal.
tuple
A tuple is an immutable sequence of zero or more values. If an object
is immutable, this means it cannot be changed once it’s been created.
Tuples are constructed using the comma to separate values. The empty
tuple, (), is a tuple containing no elements.
The elements of a tuple can be of any type—including another tuple!
The elements of a tuple needn’t be the same type. That is, tuples can be
heterogeneous.
While not strictly required by Python syntax (except in the case of
the empty tuple), it is conventional to write tuples with enclosing paren-
theses. Examples of tuples: (), (42, 71, 99), (x, y), ('cheese', 11,
True), etc.
A complete introduction to tuples appears in Chapter 10.
list
A list is a mutable sequence of zero or more values. If an object is muta-
ble, then it can be changed after it is created (we’ll see how to mutate
lists later). Lists must be created with square brackets and elements
1
In some instances, it might be helpful to interpret these as “on” and “off” but
this will vary with context.
Dynamic typing 31
within a list are separated by commas. The empty list, [], is a list con-
taining no elements.
The elements of a list can be of any type—including another list! The
elements of a list needn’t be the same type. That is, like tuples, lists can
be heterogeneous.
Examples of lists: [], ['hello'], ’['Larry', 'Moe', 'Curly'], [3, 6,
9, 12], [a, b, c], [4, 'alpha', ()], etc.
A complete introduction to lists appears in Chapter 10.
dict
dict is short for dictionary. Much like a conventional dictionary, Python
dictionaries store information as pairs of keys and values. We write dictio-
naries with curly braces. Keys and values come in pairs, and are written
with a colon separating key from value.
There are significant constraints on dictionary keys (which we’ll see
later in Chapter 16). However, dictionary values can be just about
anything—including lists, tuples, and other dictionaries! Like lists, dic-
tionaries are mutable. Example:
The first few types we’ll investigate are int (integer), float (floating-
point number), str (string), and bool (Boolean). As noted, we’ll learn
more about other types later.
For a complete reference of built-in Python types, see: https://docs
.python.org/3/library/stdtypes.html
>>> x = 1
>>> print(type(x))
<class 'int'>
>>> x = 'Hey! Now I am a string!'
>>> print(type(x))
<class 'str'>
int x = 1;
x = "Hey! Now I am a string!";
int x = 1
that makes Java statically typed. For example, other languages have type
inference but are still statically typed (Python has limited type inference).
Type inference is when the compiler or interpreter can infer something’s
type without having to be told explicitly “this is a string” or “this is an
integer.” For example, in Rust:
let x = 1;
x = "Hey! Now I am a string!";
Python does not. Python won’t enforce the correct use of types—that’s
up to you!
Find 01000001 within the bitstring2 shown in Figure 3.1. That’s the
integer value.3
Figure 3.2 shows the representation of the string 'A'.4 The letter ‘A’
is represented with the value (code point) of 65.
2
A bitstring is just a sequence of zeros and ones.
3
Actually, the value is stored in two bytes 01000001 00000000 as shown within the
box in Figure 3.1. This layout in memory will vary with the particular implementa-
tion on your machine.
4
Python uses Unicode encoding for strings. For reading on character encodings,
don’t miss Joel Spolsky’s “The Absolute Minimum Every Software Developer Abso-
lutely, Positively Must Know About Unicode and Character Sets (No Excuses!)”.
34 Types and literals
Again, find 01000001 within the bitstring Figure 3.2—that’s the en-
coding of 'A'.
Apart from both representations containing the value 65 (01000001),
notice how different the representations of an integer and a string are!
How does Python know to interpret one as an integer and the other as a
string? The type information is encoded in this representation (that’s a
part of what all the other ones and zeros are). That’s how Python knows.
That’s one reason types are crucial!
Note: Other languages do this differently, and the representations
(above) will vary somewhat depending on your machine’s architecture.
Now, you don’t need to know all the details of how Python uses your
computer’s memory in order to write effective programs, but this should
give you a little insight into one reason why we need types. What’s
important for you as a programmer is to understand that different types
have different behaviors. There are things that you can do with an integer
that you can’t do with a string, and vice versa (and that’s a good thing).
More on string literals 35
Both are syntactically valid, and Python does not differentiate between
the two.
It’s not unusual that we have a string which contains quotation marks
or apostrophes. This can motivate our choice of delimiters.
For example, given the name of a local coffee shop, Speeder and Earl’s,
there are two ways we could write this in Python. One approach would
be to escape the apostrophe within a string delimited by single quotes:
is called escaping, and it tells Python that what follows should be in-
terpreted as an apostrophe and not a closing delimiter. We refer to the
string \', as an escape sequence.7
What would happen if we left that out?
What’s going on here? Python reads the second single quote as the ending
delimiter, so there’s an extra—syntactically invalid—trailing s' at the
end.
Another approach is to use double quotations as delimiters.
The same applies to double quotes within a string. Let’s say we wanted
to print
“Medium coffee, please”, she said.
We could escape the double quotes within a string delimited by double
quotes:
However, it’s a little tidier in this case to use single quote delimiters.
7
Escape sequence is a term whose precise origins are unknown. It’s generally un-
derstood to mean that we use these sequences to “escape” from the usual meaning
of the symbols used. In this particular context, it means we don’t treat the apostro-
phe following the slash as a string delimiter (as it would otherwise be treated), but
rather as a literal apostrophe.
Representation error of numeric types 37
or
8
https://docs.python.org/3/tutorial/introduction.html#strings
9
https://docs.python.org/3/reference/lexical_analysis.html#literals
38 Types and literals
From there it’s not a great leap to see that the set of all integers
ℤ = {… , −2, −1, 0, 1, 2, …}
is infinite too.
The rational numbers, ℚ, and set of all real numbers, ℝ, also are
infinite.
This fact—that these sets are of infinite size—has implications for
numeric representation and numeric calculations on computers.
When we work with computers, numbers are given integer or floating-
point representations. For example, in Python, we have distinct types,
int and float, for holding integer and floating-point numbers, respec-
tively.
I won’t get into too much detail about how these are represented in
binary, but here’s a little bit of information.
Integers
Representation of integers is relatively straightforward. Integers are rep-
resented as binary numbers with a position set aside for the sign. So,
12345 would be represented as 00110000 00111001. That’s
10
For more, see: https://en.wikipedia.org/wiki/IEEE_754
11
That’s not entirely true for integers in Python, but it’s reasonable to think of
it this way for the purpose at hand.
40 Types and literals
>>> 0.1
0.1
>>> 0.2
0.2
>>> 0.1 + 0.2
0.30000000000000004
Wait! What? Yup. Something strange is going on. Python rounds values
when displaying in the shell. Here’s proof:
>>> print(f'{0.1:.56f}')
0.10000000000000000555111512312578270211815834045410156250
>>> print(f'{0.2:.56f}')
0.20000000000000001110223024625156540423631668090820312500
>>> print(f'{0.1 + 0.2:.56f}')
0.30000000000000004440892098500626161694526672363281250000
3.6 Exercises
Exercise 01
Give the type of each of the following literals:
a. 42
b. True
c. "Burlington"
d. -17.45
e. "100"
f. "3.141592"
g. "False"
You may check your work in the Python shell, using the built-in function
type(). For example,
>>> type(777)
<class 'int'>
Exercise 02
What happens when you enter the following in the Python shell?
a. 123.456.789
b. 123_456_789
c. hello
d. "hello'
e. "Hello" "World!" (this one may surprise you!)
f. 1,000 (this one, too, may surprise you!)
g. 1,000.234
h. 1,000,000,000
i. '1,000,000,000'
Exercise 03
The following all result in SyntaxError. Fix them!
Exercise 04 (challenge!)
We’ve seen that representation error occurs for most floating-point deci-
mal values. Can you find values in the interval [0.0, 1.0) that do not have
representation error? Give three or four examples. What do all these
examples have in common?
Chapter 4
Learning objectives
• You will learn how to use the assignment operator and how to
create and name variables.
• You will learn how to use the addition, subtraction, multiplication,
division, and exponentiation operators.
• You will learn the difference between and use cases of division and
Euclidian division (integer division).
• You will learn how to use the remainder or “modulo” operator.
• You will learn operator precedence in Python.
Terms introduced
• absolute value
• assignment
• congruence
• dividend
• divisor
• Euclidean division
• evaluation
• exception
• expression
• floor function
• modulus
• names
• operator
43
44 Variables, statements, and expressions
• quotient
• remainder
• variable
print(1 + 1)
print(2 + 2)
...
but that’s really awkward. For every sum we want to calculate, we’d have
to write another statement.
So when we write computer programs we use variables. In Python, a
variable is the combination of a name and an associated value which
has a specific type.1
It’s important to note that variables in a computer program are not
like variables you’ve learned about in mathematics. For example, in math-
ematics we might write 𝑎+𝑏 = 5 and, of course, there’s an infinite number
of possible pairs of values which sum to five.
When writing computer programs, variables are rather different.
While the same name can refer to different values at different times,
a name can refer to only one value at a time.
Assignment statements
In Python, we use the = to assign a value to a variable, and we call = the
assignment operator. The variable name is on the left-hand side of the
assignment operator, and the expression (which yields a value) is on the
right-hand side of the assignment operator.
1
Python differs from most other programming languages in this regard. In many
other programming languages, variables refer to memory locations which hold values.
(Yes, deep down, this is what goes on “under the hood” but the paradigm from the
perspective of those writing programs in Python is that variables are names attached
to values.) Feel free to check the entry in the glossary for more.
Variables and assignment 45
a = 42
it may help to say, “Let a equal 42”, or “a gets 42”, rather than “a equals
42” (which sounds more like a claim or assertion about the value of a).
This can reinforce the concept of assignment.3
Dynamic typing
In Python, all values have a type, and Python knows the type of each
value at every instant. However, Python is a dynamically typed language.
This means that any given name can refer to values of different types at
different points in a program. So this is valid Python:
x = 0
print(x) # prints 0 to the console
x = x + 1
print(x) # prints 1 to the console
x = x + 1
print(x) # prints 2 to the console
x = 0
2
In fact, the left-facing arrow is commonly used to indicate assignment in pseu-
docode—descriptions of algorithms outside the context of any particular program-
ming language.
3
Later on, we’ll see the comparison operator ==. This is used to compare two
values to see if they are identical. For example, a == b would be true if the values of
a and b were the same. So it’s important to keep the assignment (=) and comparison
(==) operators straight in your mind.
46 Variables, statements, and expressions
we’re assigning the literal value 0 to x. At this point we can say the value
of x is 0.
Consider what happens here:
x = x + 1
So first, Python will evaluate the expression on the right, and then it
will assign the result to x. At the start, the value of x is still zero, so we
can think of Python substituting the value of x for the object x on the
right hand side.
x = 0 + 1
x = 1
x = x + 1
now the x on the right has the value 1, and 1 + 1 is 2, so the variable x
has the value 2.
>>> x = 1001
>>> y = x
What we’ve done here is give two different names to the same value. This
is A-OK in Python. What does x refer to? The value 1001. What does
y refer to? The exact same 1001.4 It is not the case that there are two
different locations in memory both holding the value 1001 (as might be
the case in a different programming language).
Now what happens if we assign a new value to x? Does y “change”?
What do you think?
>>> x = 2001
>>> x
2001
>>> y
1001
4
We can verify this by inspecting the identity number of the object(s) in question
using Python’s built-in id() function.
Variables and assignment 47
No. Even though x now has the new value of 2001, y is unchanged and
still has the value of 1001.
When we assign a value to a variable,
>>> x = 1001
>>> x = 1001
>>> y = x
>>> z = y
>>> x = 500
>>> x
500
>>> y
1001
>>> z
1001
y and z are still names for 1001, but now the name x is associated with
a new value, 500.
While it’s true that values can have more than one name associated
with them, it’s important to understand that each name can only refer
to a single value (or object). x can’t have two different values at the same
time.
>>> x = 3
>>> x
3
>>> x = 42 # What happened to 3? Gone forever.
>>> x
42
48 Variables, statements, and expressions
Comprehension check
Given the following snippets of Python code, determine the resulting
value x:
1.
x = 1
2.
x = 1
x = x + 1
3.
y = 200
x = y
4.
x = 0
x = x * 200
5.
x = 1
x = 'hello'
6.
x = 5
y = 3
x = x + 2 * y - 1
Constants
A lot of the time in programming, we want to use a specific value or
calculation multiple times. Instead of repeating that same value or cal-
culation over and over again, we can just assign the value to a variable
and reuse it throughout a program. We call this constant. A constant
is a variable that has a value that will be left unchanged throughout a
program. Using constants improves the readability of programs because
they provide meaningful and recognizable names for fixed values. Let’s
look at an example:
HOURS_IN_A_DAY = 24
Expressions 49
4.2 Expressions
In programming—and computer science in general—an expression is
something which can be evaluated—that is, a syntactically valid combi-
nation of constants, variables, functions, and operators which yields a
value.
Let’s try out a few expressions with the Python shell.
>>> 1
1
Once again, we’ve provided a single literal, and again, Python has replied
with its value.
You may notice that 'Hello, Python!' is rather different from 1. You
might say these are literals of different types—and you’d be correct! Liter-
als come in different types. Here are four different literals of four different
types.
>>> 3.141592
3.141592
>>> True
True
What types are these? 3.141592 is a floating point literal (that’s a number
that has something to the right of the decimal point). True is what’s called
a Boolean literal. Notice there are no quotation marks around it and the
first letter is capitalized. True and False are the only two Boolean literals.
>>> 1 + 2
3
Surprised? Probably not. But let’s consider what just happened any-
way.
At the prompt, we typed 1 + 2 and Python responded with 3. 1 and
2 are integer literals, and + is the operator for addition. 1 and 2 are the
operands, and + is the operator. This combination 1 + 2 is a syntactically
valid Python expression which evaluates to… you guessed it, 3.
Some infix arithmetic operators in Python are:
+ addition
- subtraction
* multiplication (notice we use * and not x)
/ division
// integer or “floor” division
% remainder or “modulo”
** exponentiation
There are other operators, but these will suffice for now. Here we’ll
present examples of the first four, and we’ll present the others later—
floor division, modulo, and exponentiation. Let’s try a few (I encourage
you follow along and try these out in the Python shell as we go).
>>> 40 + 2
42
>>> 3 * 5
15
Expressions 51
>>> 5 - 1
4
>>> 30 / 3
10.0
Notice that in the last case, when performing division, Python returns
a floating-point number and not an integer (Python does support what’s
called integer division or floor division, but we’ll get to that later). So
even if we have two integer operands, division yields a floating-point
number.
What do you think would be the result if we were to add the following?
>>> 1 + 1.0
>>> 1 + 1.0
2.0
>>> 2 - 1.0
1.0
>>> 3 * 5.0
15.0
Precedence of operators
No doubt you’ve learned about precedence of operations, and Python
respects these rules.
>>> 40 + 2 * 3
46
>>> 3 * 5 - 1
14
>>> 30 - 18 / 3
24.0
>>> 40 + (2 * 3)
46
>>> 3 * (5 - 1)
12
>>> (30 - 18) / 3
4.0
So what happens here? The portions within the parentheses are eval-
uated first, and then Python performs the remaining operation.
We can construct expressions of arbitrary complexity using these
arithmetic operators and parentheses.
>>> (1 + 1) * (1 + 1 + 1) - 1
5
Python also has unary operators. These are operators with a single
operand. For example, we negate a number by prefixing -.
>>> -1
-1
>>> -1 + 3
2
>>> 1 + -3
-2
>>> -(3 * 5)
-15
** exponentiation
+, - unary positive or negative (+x, -x)
*, /, //, % multiplication, and various forms of division
+, - addition and subtraction (x - y, x + y)
Comprehension check
1. When evaluating expressions, do you think Python proceeds left-to-
right or right-to-left? Can you think of an experiment you might
perform to test your hypothesis? Write down an expression that
might provide some evidence.
2. Why do you think 1 / 1 evaluates to 1.0 (a float) and not just 1
(an integer)?
More on operations
So far, we’ve seen some simple expressions involving literals, operators,
and parentheses. We’ve also seen examples of a few types: integers,
floating-point numbers (“floats” for short), strings, and Booleans.
We’ve seen that we can perform arithmetic operations on numeric
types (integers and floats).
>>> 'Foo' * 1
'Foo'
>>> 'Foo' * 2
'FooFoo'
>>> 'Foo' * 3
'FooFooFoo'
>>> 'Foo' * 0
This gives us '' which is called the empty string and is the result of
concatenating zero copies of 'Foo' together. Notice that the result is
still a string, albeit an empty one.
54 Variables, statements, and expressions
>>> a = 0
>>> a += 1
>>> a
1
>>> a += 1
>>> a
2
>>> a -= 1
>>> a
1
>>> a -= 1
>>> a
0
You may ask: What’s the difference between the division we saw ear-
lier, /, and floor division with //? The difference is that / calculates the
quotient as a decimal expansion. Here’s a simple comparison:
>>> 4 / 3
1.3333333333333333 # three goes into four 1 and 1/3 times
>>> 4 // 3 # calculates Euclidean quotient
1
>>> 4 % 3 # calculates remainder
1
Common questions
What happens when the divisor is zero?
Just as in mathematics, we cannot divide by zero in Python either. So
all of these operations will fail if the right operand is zero, and Python
will complain: ZeroDivisionError.
>>> 5 % 7
5
That is, seven goes into five zero times and leaves a remainder of five.
So if 𝑚 < 𝑛, then m % n yields m.
>>> 15 // -5
-3
>>> 15 % -5
0
>>> 17 // -5
-4
>>> 17 % -5
-3
>>> -15 // 5
-3
>>> -15 % 5
0
>>> -17 // 5
-4
>>> -17 % 5
3
>>> -43 // -3
14
>>> -43 % -3
-1
The % operator will always yield a result with the same sign as the
second operand (or zero).
You are encouraged to experiment with the Python shell. It is a great
tool to further your understanding.
Perhaps you don’t realize it, but you do modular arithmetic in your
head all the time. For example, if you were asked what time is 5 hours
after 9 o’clock, you’d answer 2 o’clock. You wouldn’t say 14 o’clock.8
This is an example of modular arithmetic. In fact, modular arithmetic is
sometimes called “clock arithmetic.”
7
https://docs.python.org/3/reference/expressions.html
8
OK. Maybe in the military or in Europe you might, but you get the idea. We
have a clock with numbers 12–11, and 12 hours brings us back to where we started
(at least as far as the clock face is concerned). Notice also that the arithmetic is the
same for an analog clock face with hands and a digital clock face. This difference
in interface doesn’t change the math at all, it’s just that visually things work out
nicely with the analog clock face.
60 Variables, statements, and expressions
4
1
3 2
Figure 4.4: A “clock” for (mod 5)
4
1
3 2
Figure 4.5: 4 + 1 ≡ 0 (mod 5)
4
1
3 2
Figure 4.6: 4 + 2 ≡ 1 (mod 5)
62 Variables, statements, and expressions
4
1
3 2
Figure 4.7: 4 + 3 ≡ 2 (mod 5)
4
1
3 2
Figure 4.8: 1 − 3 ≡ 3 (mod 5)
Negative modulus
We’ve seen that when we add we go clockwise, and when we subtract we
go anti-clockwise. What happens when the modulus is negative?
To preserve the “direction” of addition (clockwise) and subtraction
(anti-clockwise), if our modulus is negative we number the face of the
clock anti-clockwise.
Examples:
1 ≡ −4 (mod − 5)
2 ≡ −3 (mod − 5)
2 + 4 ≡ −4 (mod − 5)
We can confirm these agree with Python’s evaluation of these expres-
sions:
>>> 1 % -5
-4
>>> 2 % -5
Modular arithmetic 63
−1
−4
−2 −3
Figure 4.9: A “clock” for (mod -5)
−1
−4
−2 −3
Figure 4.10: 1 ≡ −4 (mod − 5)
−1
−4
−2 −3
Figure 4.12: 2 + 4 ≡ −4 (mod − 5)
-3
>>> (4 + 2) % -5
-4
What now?
This is actually a big topic, involving equivalence classes, remainders (in
this context, called “residues”), and others. That’s all outside the scope
of this textbook. Practically, however, there are abundant applications
for modular arithmetic, and this is something we’ll see again and again
in this text.
Some applications for modular arithmetic include:
• Hashing function
• Cryptography
• Primality and divisibility testing
• Number theory
EGGS_PER_CARTON = 12
cartons = n // EGGS_PER_CARTON
leftover = n % EGGS_PER_CARTON
if n % 2 == 0:
print(f'{n} is even')
else:
print(f'{n} is odd')
(Yes, there’s an even simpler way to write this. We’ll get to that in due
course.)
Comprehension check
1. Given some modulus, 𝑛, an integer, and some dividend, 𝑑, also an
integer, what are the possible values of d % n if
a. 𝑛=5
b. 𝑛 = −4
c. 𝑛=2
d. 𝑛=0
4.6 Exponentiation
Exponentiation is a ubiquitous mathematical operation. However, the
syntax for exponentiation varies between programming languages. In
some languages, the caret (^) is the exponentiation operator. In other lan-
guages, including Python, it’s the double-asterisk (**). Some languages
don’t have an exponentiation operator, and instead they provide a library
function, pow().
The reasons for these differences are largely historical. In mathematics,
we write an exponent as a superscript, e.g., 𝑥2 . However, keyboards and
character sets don’t know about superscripts,10 and so the designers of
programming languages had to come up with different ways of writing
exponentiation.
** was first used in Fortran, which first appeared in 1957. This is the
operator which Python uses for exponentiation.
For the curious, here’s a table with some programming languages and
the operators or functions they use for exponentiation.
Exponentiation in Python
Now we know that ** is the exponentiation operator in Python. This
is an infix operator, meaning that the operator appears between its two
operands. As you’d expect, the first operand is the base, and the second
operand is the exponent or power. So,
b ** n
implements 𝑏𝑛 .
Here are some examples,
>>> 3 ** 2
9
>>> 3.0 ** 2
9.0
>>>
>>> pow(3, 2)
9
>>> pow(3.0, 2)
9.0
No surprises here.
𝑥0 = 1
Remember from algebra that any non-zero number raised to the zero
power is one. If that weren’t the case, what would become of this rule?
𝑏𝑚+𝑛 = 𝑏𝑚 × 𝑏𝑛
So 𝑥0 = 1 for all non-zero 𝑥. Python knows about that, too.
>>> 1 ** 0
1
>>> 2 ** 0
1
>>> 0.1 ** 0
1.0
What about 00 ? Many mathematics texts state that this should be un-
defined or indeterminate. Others say 00 = 1. What do you think Python
does?
68 Variables, statements, and expressions
>>> 0 ** 0
1
A little puzzle
Consider the following Python shell session:
>>> pow(-1, 0)
1
>>> -1 ** 0
-1
What’s going on here? The answer we get using pow() is what we’d expect.
Shouldn’t these both produce the same result? Can you guess why these
yield different answers?
4.7 Exceptions
Exceptions are errors that occur at run time, that is, when you run your
code. When such an error occurs Python raises an exception, prints a
message with information about the exception, and then halts execution.
Exceptions have different types, and this tells us about the kind of error
that’s occurred.
If there is a syntax error, an exception of type SyntaxError is raised.
If there is an indentation error (a more specific kind of syntax error), an
IndentationError is raised. These errors occur before your code is ever
run—they are discovered as Python is first reading your file.
Most other exceptions occur as your program is run. In these cases,
the message will include what’s called a traceback, which provides a little
information about where in your code the error occurred. The last line
in an exception message reports the type of exception that has occurred.
It’s often helpful to read such messages from the bottom up.
What follows are brief summaries of the first types of exceptions you’re
likely to encounter, and in each new chapter, we’ll introduce new excep-
tion types as appropriate.
SyntaxError
If you write code which does not follow the rules of Python syntax,
Python will raise an exception of type SyntaxError. Example:
>>> 1 + / 1
File "<stdin>", line 1
1 + / 1
^
SyntaxError: invalid syntax
Exceptions 69
Notice that the ^ character is used to indicate the point at which the
error occurred.
Here’s another:
NameError
A NameError occurs when we try to use a name which is undefined. There
must be a value assigned to a name before we can use the name.
Here’s an example of a NameError:
>>> print(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
Notice that Python reports the NameError and informs you of the name
you tried to use but which is undefined (in this case x).
These kinds of errors most often occur when we’ve made a typo in a
name.
Depending on the root cause of the error, there are two ways to correct
these errors.
TypeError
A TypeError occurs when we try to perform an operation on an object
which does not support that operation.
The Python documentation states: “[TypeError is] raised when an op-
eration or function is applied to an object of inappropriate type. The
associated value is a string giving details about the type mismatch.”11
For example, we can perform addition with operands of type int using
the + operator, and we can concatenate strings using the same operator,
but we cannot add an int to a str.
>>> 2 + 2
4
>>> 'fast' + 'fast' + 'fast'
'fastfastfast'
>>> 2 + 'armadillo'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'
When you encounter a TypeError, you must examine the operator and
operands and determine the best fix. This will vary on a case-by-case
basis.
Here are some other examples of TypeError:
>>> 'hopscotch' / 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for /: 'str' and 'int'
>>> 'barbequeue' + 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
ZeroDivisionError
Just as we cannot divide by zero in mathematics, we cannot divide by
zero in Python either. Since the remainder operation (%) and integer
(a.k.a. floor) division (//) depend on division, the same restriction applies
to these as well.
11
https://docs.python.org/3/library/exceptions.html#TypeError
Exercises 71
>>> 1000 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
4.8 Exercises
Exercise 01
Without typing these at a Python prompt first, determine the value of
each of the following expressions. Once you’ve worked out what you think
the evaluation should be, check your answer using the Python shell.
a. 13 + 6 - 1 * 7
b. (17 - 2) / 5
c. -5 / -1
d. 42 / 2 / 3
e. 3.0 + 1
f. 1.0 / 3
g. 2 ** 2
h. 2 ** 3
i. 3 * 2 ** 8 + 1
Exercise 02
For each of the expressions in exercise 01, give the type of the result of
evaluation. Example: 1 + 1 evaluates to 2 which is of type int.
Exercise 03
What is the evaluation of the following expressions?
a. 10 % 2
b. 19 % 2
c. 24 % 5
d. -8 % 3
72 Variables, statements, and expressions
Exercise 04
What do you think would happen if we were to use the operands we’ve
just seen with non-numeric types? For example, what do you think would
happen if we were to enter the following. Then check your expectations
using the Python interpreter. Any surprises?
b. 'Hello' * 3
c. True * True
d. True * False
e. False * 42
f. -True
g. True + True
Exercise 05
What is the difference between the following statements?
it_is_cloudy_today = True
it_will_rain_tomorrow = 'True'
Exercise 06
Some operands don’t work with certain types. For example, the following
will result in errors. Try these out at a prompt, and observe what happens.
Make a note of the type of error which occurs.
a. 'Hello' / 3
b. -'Hello'
c. 'Hello' - 'H'
Exercise 07
a. Write a statement that assigns the value 79.95 to a variable name
subtotal.
Exercise 08
What do you think would happen if we were to evaluate the expression
1 / 0? Why? Does this result in an error? What type of error results?
Exercise 09
a. Now that we’ve learned a little about modular arithmetic, recon-
sider the numerals we use in our decimal (base 10) system. In that
system, why do we have only the numerals 0, 1, 2, 3, 4, 5, 6, 7, 8,
and 9?
b. What numerals would we need in a base 7 system? How about base
5?
Chapter 5
Functions
Learning objectives
• You will learn how to write simple functions in Python, using def.
• You will learn how to use functions in your code.
• You will learn that indentation is syntactically meaningful in
Python (unlike many other languages).
• You will learn how to use functions (and constants) in Python’s
math module, such as square root and sine.
• You will expand on and solidify your understanding of topics pre-
sented in earlier chapters.
75
76 Functions
Here we’re making use of built-in Python function print(). The de-
velopers of Python have written this function for you, so you can use it
within your program. When we use a function, we say we are “calling”
or “invoking” the function.
In the example above, we call the print() function, supplying the
string 'Do you want a cookie?' as an argument.
As a programmer using this function, you don’t need to worry about
what goes on “under the hood” (which is quite a bit, actually). How
convenient!
When we call a function, the flow of control within our program passes
to the function, the function does its work, and then returns a value. All
Python functions return a value, though in some cases, the value returned
is None.1
Defining a function
Python allows us to define our own functions.2 A function is a unit
of code which performs some calculation or some task. A function may
take zero or more arguments (inputs to the function). The definition
of a function may include zero or more formal parameters which are,
essentially, variables that will take on the values of the arguments pro-
vided. When called, the body of the function is executed. In most, but
not all cases, a value is explicitly returned. Returned values might be the
result of a calculation or some status indicator—this will vary depending
on the purpose of the function. If a value is not explicitly returned, the
value None is returned implicitly.
Let’s take the simple example of a function which squares a number.
In your mathematics class you might write
𝑓(𝑥) = 𝑥2
and you would understand that when we apply the function 𝑓 to some
argument 𝑥 the result is 𝑥2 . For example, 𝑓(3) = 9. Let’s write a function
in Python which squares the argument supplied:
1
Unlike C or Java, there is no such thing as a void function in Python. All
Python functions return a value, even if that value is None.
2
Different languages have different names for sub-programs we can call within
a larger program, e.g., functions, methods, procedures, subroutines, etc., and some
of these designations vary with context. Also, these are defined and implemented
somewhat differently in different languages. However, the fundamental idea is similar
for all: these are portions of code we can call or invoke within our programs.
Introduction to functions 77
def square(x):
return x * x
def is a Python keyword, short for “define”, which tells Python we’re
defining a function. (Keywords are reserved words that are part of
the syntax of the language. def is one such keyword, and we will see
others soon.) Functions defined with def must have names (a.k.a., “iden-
tifiers”),3 so we give our function the name “square”.
Now, in order to calculate the square of something we need to know
what that something is. That’s where the x comes in. We refer to this as
a formal parameter of the function. When we use this function elsewhere
in our code we must supply a value for x. Values passed to a function are
called arguments (however, in casual usage it’s not uncommon to hear
people use “parameter” and “argument” interchangeably).
At the end of the first line of our definition we add a colon. What
follows after the colon is referred to as the body of the function. It is
within the body of the function that the actual work is done. The body
of a function must be indented as shown below—this is required by the
syntax of the language. It is important to note that the body of the
function is only executed when the function is called, not when it is
defined.
In this example, we calculate the square, x * x, and we return the
result. return is a Python keyword, which does exactly that: it returns
some value from a function.
Let’s try this in the Python shell to see how it works:
Here we’ve defined the function square(). Notice that if we enter this
in the shell, after we hit return after the colon, Python replies with ...
and indents for us. This is to indicate that Python expects the body
of the function to follow. Remember: The body of a function must be
indented. Indentation in Python is syntactically significant (which might
seem strange if you’ve coded in Java, C, C++, Rust, JavaScript, C#,
etc.; Python uses indentation rather than braces).
So we write the body—in this case, it’s just a single line. Again,
Python replies with ..., essentially asking “Is there more?”. Here we
hit the return/enter key, and Python understands we’re done, and we
wind up back at the >>> prompt.
Now let’s use our function by calling it. To call a function, we give
the name, and we supply the required argument(s).
3
Python does allow for anonymous functions, called “lambdas”, but that’s for
another day. For the time being, we’ll be defining and calling functions as demon-
strated here.
78 Functions
Notice that once we define our function we can reuse it over and over
again. This is one of the primary motivations for functions.
Notice also that in this case, there is no x outside the body of the
function.
>>> x
Traceback (most recent call last):
File "/blah/blah/code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
NameError: name 'x' is not defined
In this case, x exists only within the body of the function.4 The ar-
gument we supply within the parentheses becomes available within the
body of the function as x. The function then calculates x * x and returns
the value which is the result of this calculation.
If we wish to use the value returned by our function we can save it by
assigning the value to some variable, or use it in an expression, or even
include it as an argument to another function!
def cube(x):
y = x ** 3 # assign result local variable `y`
return y # return the value of `y`
4
This is what is called “scope”, and in the example given x exists only within
the scope of the function. It does not exist outside the function—for this we say “x
is out of scope.” We’ll learn more about scope later.
Introduction to functions 79
>>> a = 17
>>> b = square(a)
>>> b
289
>>> PI = 3.1415926
>>> r = 126.1
>>> PI * square(r)
49955.123667046
>>> print(square(12))
144
But does print() return a value? How would you find out? Can you
think of a way you might check this?
What do you think would happen here?
Let’s see:
None is Python’s special way of saying “no value.” None is the default value
returned by functions which don’t otherwise return a value. All Python
functions return a value, though in some cases that value is None.6 So
print() returns the None.
How do we return None (assuming that’s something we want to do)?
By default, in the absence of any return statement, None will be returned
implicitly.
Using the keyword return without any value will also return None.
6
If you’ve seen void in C++ or Java you have some prior experience with func-
tions that don’t return anything. All Python functions return a value, even if that
value is None.
A deeper dive into functions 81
Comprehension check
1. Write a function which calculates the successor of any integer. That
is, given some argument n the function should return n + 1.
2. What’s the difference between a formal parameter and an argu-
ment?
3. When is the body of a function executed?
def f(x):
return x ** 2 - 1
"""
Demonstration of a function
"""
y = f(12)
print(y)
y = f(5)
print(y)
Calling a function
Once we have written our function, we may call or invoke the function
by name, supplying the necessary arguments. To call the function f()
above, we must supply one argument.
y = f(12)
This calls the function f(), with the argument 12 and assigns the result
to a variable named y. Now what happens?
"""
Demonstration of a function
"""
def f(x):
return x ** 2 - 1
if __name__ == '__main__':
y = f(5)
print(y)
performed the assignment x = 12 as the first line within the body of the
function.
"""
Demonstration of a function
"""
y = f(12)
print(y)
y = f(5)
print(y)
Once the formal parameter has been assigned the value of the argument,
the function does its work, executing the body of the function.
"""
Demonstration of a function
""" Now, with x = 12, the function evaluates
the expression…
def f(x):
return x ** 2 - 1 …12 ⨉ 12 - 1
…144 - 1
if __name__ == '__main__': …143
y = f(12)
print(y)
y = f(5)
print(y)
Then, the function returns the result. Flow of control is returned to the
point at which the function was called.
84 Functions
"""
Demonstration of a function
"""
def f(x):
The function returns the calculated
return x ** 2 - 1
value (143)…
if __name__ == '__main__':
y = f(5)
print(y)
"""
Demonstration of a function
"""
def f(x):
return x ** 2 - 1
if __name__ == '__main__':
y = f(12)
print(y) Prints 143 at the console.
y = f(5)
print(y)
Let’s call the function again, this time with a different argument, 5.
"""
Demonstration of a function
"""
def f(x):
return x ** 2 - 1
if __name__ == '__main__':
y = f(12)
print(y)
"""
Demonstration of a function
"""
y = f(12)
print(y)
y = f(5)
print(y)
"""
Demonstration of a function
""" Now, with x = 5, the function evaluates
the expression…
def f(x):
return x ** 2 - 1 …5 ⨉ 5 - 1
…25 - 1
if __name__ == '__main__': …24
y = f(12)
print(y)
y = f(5)
print(y)
"""
Demonstration of a function
"""
def f(x):
The function returns the calculated
return x ** 2 - 1
value (24)…
if __name__ == '__main__':
y = f(5)
print(y)
86 Functions
"""
Demonstration of a function
"""
def f(x):
return x ** 2 - 1
if __name__ == '__main__':
y = f(12)
print(y)
y = f(5)
print(y) Prints 24 at the console.
y = 2
def square(x):
return x ** y
y = 3
def square(x):
y = 2
return x ** y
8
Again, we’re excluding from consideration functions with optional arguments
or keyword arguments.
Passing arguments to a function 87
def square(x):
return x ** 2
def f(z):
z = z + 1
return z
9
If you’re curious, check out David Parnas’ seminal 1972 article: “On the Criteria
To Be Used in Decomposing Systems into Modules”, Communications of the ACM,
15(12) (https://dl.acm.org/doi/pdf/10.1145/361598.361623).
88 Functions
x = 5
y = f(x)
print(x) # prints 5
print(y) # prints 6
x = 1
y = 2
5.4 Scope
Names of formal parameters and any local variables created within a
function have a limited lifetime—they exist only until the function is
done with its work. We refer to this as scope.
The most important thing to understand here is that names of formal
parameters and names of local variables we define within a function have
local scope. They have a lifetime limited to the execution of the function,
and then those names are gone.
Scope 89
>>> y = foo()
>>> y
1
The name x within the function foo only exists as long as foo is being
executed. Once foo returns the value of x, x is no more.
>>> y = foo()
>>> y
1
>>> x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
So the name x lives only within the execution of foo. In this example,
the scope of x is limited to the function foo.
Shadowing
It is, perhaps, unfortunate that Python allows us to use variable names
within a function that exist outside the function. This is called shadowing,
and it sometimes leads to confusion.
Here’s an example:
within the function square is local, within that function. Yes, it has the
same name as the x in the outer scope, but it’s a different x.
Generally, it’s not a good idea to shadow variable names in a function.
Python allows it, but this is more a matter of style and avoiding confu-
sion. Oftentimes, we rename the variables in our functions, appending
an underscore.
def successor(n):
return n + 1
Impure functions
Sometimes it’s useful to implement an impure function. An impure func-
tion is one that has side effects. For example, we might want to write a
function that prompts the user for an input and then returns the result.
def get_price():
while True:
price = float(input("Enter the asking price "
"for the item you wish "
"to sell: $"))
if price > 1.00:
break
The math module 91
else:
print("Price must be greater than $1.00!")
return price
This is an impure function since it has side effects, the side effects
being the prompts and responses displayed to the user. That is, we can
observe behavior in this function other than its return value. It does
return a value, but it exposes other behaviors as well.
Comprehension check
1. Write an impure function which produces a side effect, but returns
None.
Here we’ll learn a little about Python’s math module. The Python math
module is a collection of constants and functions that you can use in your
own programs—and these are very, very convenient. For example, why
92 Functions
write your own function to find the principal square root of a number
when Python can do it for you?10
Unlike built-in functions, in order to use functions (and constants)
provided by the Python math module, we must first import the module
(or portions thereof).11 You can think of this as importing features you
need into your program.
Python’s math module is rich with features. It provides many functions
including
function calculation
√
sqrt(x) 𝑥
exp(x) 𝑒𝑥
log(x) ln 𝑥
log2(x) log2 𝑥
sin(x) sin 𝑥
cos(x) cos 𝑥
Now what? Say we’d like to use the sqrt() function provided by the
math module. How do we access it?
If we want to access functions (or constants) within the math module
we use the . operator.
Let’s unpack this. Within the math module, there’s a function named
sqrt(). Writing math.sqrt() is accessing the sqrt() function within the
math module. This uses what is called dot notation in Python (and many
other languages use this as well).
Let’s try another function in the math module, sin(), which calculates
the sine of an angle. You may remember from pre-calculus or trigonome-
try course that sin 0 = 0, sin 𝜋2 = 1, sin 𝜋 = 0, sin 3𝜋
2 = −1, and sin 2𝜋 = 0.
Let’s try this out.
10
The only reasonable answer to this question is “for pedagogical purposes”, and
in fact, later on, we’ll do just that—write our own function to find the principal
square root of a number. But let’s set that aside for now.
11
We’re going to ignore the possibility of importing portions of a module for now.
Exceptions 93
So far, so good.
>>> math.sin(PI / 2)
0.999999998926914
That’s close, but not quite right. What went wrong? (Hint: Repre-
sentation error is not the problem.) Our approximation of 𝜋 (defined as
PI = 3.14159, above) isn’t of sufficient precision. Fortunately, the math
module includes high-precision constants for 𝜋 and 𝑒.
>>> math.sin(math.pi / 2)
1.0
Much better.
It is left to the reader to test other arguments to the sin() function.
5.7 Exceptions
IndentationError
In Python, unlike many other languages, indentation is syntactically sig-
nificant. We’ve seen when defining a function that the body of the func-
tion must be indented (we’ll see other uses of indentation soon).
An exception of type IndentationError is raised if Python dis-
agrees with your use of indentation—typically if indentation is expected
and your code isn’t, or if a portion of your code is over-indented.
IndentationError is a more specific type of SyntaxError.
When you encounter an indentation error, you should inspect your
code and correct the indentation.
Here’s an example:
ValueError
A ValueError is raised when the type of an argument or operand is valid,
but the value is somehow unsuitable. We’ve seen how to import the math
module and how to use math.sqrt() to calculate the square root of some
number. However, math.sqrt() does not accept negative operands (it
doesn’t know about complex numbers), and so, if you supply a negative
operand to math.sqrt() a ValueError is raised.
Example:
In a case like this, you need to ensure that the argument or operand
which is causing the problem has a suitable value.
ModuleNotFoundError
We encounter ModuleNotFoundError if we try to import a module that
doesn’t exist, that Python can’t find, or if we misspell the name of a
module.
Example:
5.8 Exercises
Exercise 01
Identify the formal parameter(s) in each of the following functions.
a.
def add_one(n):
n = n + 1
return n
b.
Exercise 02
There’s something wrong with each of these function definitions. Identify
the problem and suggest a fix.
def cube:
return x ** 3
def say_hello():
print(name)
def poly(x):
return x ** 2 + 3 * x - 1
def subtract_one(x):
y = x - 1
Exercise 03
Write a function which takes any arbitrary string as an argument and
prints the string to the console.
96 Functions
Exercise 04
Write a function which takes two numeric arguments (float or int) and
returns their product.
Exercise 05
a. Write a function which take an integer as an argument and returns 0
if the integer is even and 1 if the integer is odd. Hint: The remainder
(modulo) operator, %, calculates the remainder when performing
integer division. For example, 17 % 5 yields 2, because 5 goes into
17 three times, leaving a remainder of two.
b. What did you name your function and why?
Exercise 06
Write a function which takes two numeric arguments, one named
subtotal and the other named tax_rate, and calculates and returns the
total including tax. For example, if the arguments supplied were 114.0
for subtotal and 0.05 for tax_rate, your function should return the value
119.7. If the arguments were 328.0 and 0.045, your function should re-
turn the value 342.76.
This function should produce no side effects.
Chapter 6
Style
Programs must be written for people to read, and only
incidentally for machines to execute.
–Abelson, Sussman, and Sussman
This chapter will introduce the concept of good Python style through
the use of the PEP 8 style guide. We’ll further our understanding of
constants, learn about the benefits of using comments, and how/when
to use them effectively.
Learning objectives
• You will learn about the importance of good Python style and the
PEP 8 style guide.
• You will learn conventions for naming constants.
• You will learn about comments and docstrings in Python and their
uses.
Terms introduced
• camelCase
• docstring
• inline comment
• PEP 8
• single-line comment
• snake_case
good style:
6.2 PEP 8
Fortunately, there is a long-standing, comprehensive style guide for
Python called PEP 8.2 The entire style guide PEP 8 – Style Guide
for Python Code is available online3 . Noted Python developer Kenneth
Reitz has made a somewhat prettified version at https://pep8.org/. You
should consult these resources for a complete guide to good style for
Python. Many projects in industry and in the open source world enforce
the use of this standard.
Don’t think in terms of saving keystrokes—follow the style guide, and
you’ll find this helps you (and others!) read and understand what you
write.
6.3 Whitespace
Yes! Whitespace is important! Using whitespace correctly can make your
code much more readable.
Use whitespace between operators and after commas.4 Example:
# Don't do this
x=3.4*y+9-17/2
joules=(kg*m**2)/(sec**2)
amount=priceperitem*items
Instead, do this:
1
https://google.github.io/styleguide/pyguide.html
2
PEP is short for Python Enhancement Proposal
3
https://peps.python.org/pep-0008/
4
One exception to this rule is keyword arguments in function calls, e.g., the end
keyword argument for the built-in print() function.
Whitespace 99
# Better
x = 3.4 * y + 9 - 17 / 2
Without the whitespace between operators, your eye and brain have to
do much more work to divide these up for reading.
Sometimes, however, extra whitespace is unnecessary or may even hin-
der readability. You should avoid unnecessary whitespace before closing
and after opening braces, brackets or parentheses.
# Don't do this
# Better
picas = inches_to_picas(inches)
# Don't do this
# Better
def inches_to_points(in):
return in * POINTS_PER_INCH
# Don't do this
lst = [3 , 2 , 1]
# Better
lst = [3, 2, 1]
Compare these with the good names (above). With a good name, you
know what the variable represents. With these bad names, who knows
what they mean? (Seriously, what the heck is rsolln?)
There are some particularly bad names that should be avoided no
matter what. Never use the letter ”O” (upper or lower case) or ”l” (low-
ercase ”L”) or ”I” (upper case ”i”) as a variable name. “O” and “o” look
too much like “0”. “l” and “I” look too much like “1”.
While single letter variable names are, in general, to be avoided, it’s
OK sometimes (depending on context). For example, it’s common prac-
tice to use i, j, etc. for loop indices (we’ll get to loops later) and x, y, z
for spatial coordinates, but only use such short names when it is 100%
clear from context what these represent.
Similar rules apply to functions. Examples of bad function names:
• i2m()
• sd()
Throughout this text, you may notice some techniques used to keep
line length in code samples within these bounds.
6.6 Constants
In most programming languages there’s a convention for naming con-
stants. Python is no different—and the convention is quite similar to
many other languages.
In Python, we use ALL_CAPS for constant names, with underscores
to separate words if necessary. Here are some examples:
# Physical constants
C = 299792458 # speed of light: meters / second ** -1
MASS_ELECTRON = 9.1093837015 * 10 ** -31 # mass in kg
# Mathematical constants
PI = 3.1415926535 # pi
PHI = 1.6180339887 # phi (golden ratio)
# Unit conversions
FEET_PER_METER = 3.280839895
KM_PER_NAUTICAL_MILES = 1.852
# Other constants
EGGS_PER_CARTON = 12
Unlike Java, there is no final keyword, which tells the compiler that a
constant must not be changed (same for other languages like C++ or
Rust which have a const keyword).
What prevents a user from changing a constant? In Python, nothing.
All the more reason to make it immediately clear—visually—that we’re
dealing with a constant.
So the rule in Python is to use ALL_CAPS for constants and noth-
ing else. Then it’s up to you, the programmer, to ensure these remain
unchanged.
102 Style
That said, here are some forms and guidelines for comments and doc-
strings.
Docstrings
Docstring is short for documentation string. These are somewhat different
from comments. According to PEP 257
Docstrings are not ignored by the Python interpreter, but for the
purposes of this textbook you may think of them that way. Docstrings
are delimited with triple quotation marks. Docstrings may be single lines,
thus:
def square(n):
"""Return the square of n."""
return n * n
"""
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210, section Z
Homework 5
"""
"""
Distance converter
J. Jones
This approach allows you to design your program initially without fuss-
ing with syntax or implementation details, and then, once you have the
outline sketched out in comments, you can focus on the details one step
at a time.
Avoid over-commenting
While it is good practice to include comments in your code, well-written
code often does not require much by way of comments. Accordingly, it’s
important not to over-comment your code. Here are some examples of
over-commenting:
Ĺ Note
6.8 Exercises
Exercise 01
Here are some awful identifiers. Replace them with better ones.
# Circumference of a circle
circle = rad ** 2 * math.pi
# Clock arithmetic
# Calculate 17 hours after 7 o'clock
thisIsHowWeDoItInJava = (7 + 17) % 12
Exercise 02
The following Python code runs perfectly fine but deviates from the PEP
8 style guide. Using your chosen IDE, fix the issues and check to make
sure that the program still runs correctly.
def CIRCUMFERENCEOFCIRCLE(radius):
c=2*pi*radius
return c
pi=3.14159
CIRC=CIRCUMFERENCEOFCIRCLE(22)
print("The circumference of a circle with a radius of 22cm"
"is "+str(CIRC)+ "cm.")
Chapter 7
Console I/O
So far, we’ve provided all the values for our functions in our code. Things
get a lot more interesting when the user can provide such values.
In this chapter, we’ll learn how to get input from the user and use it
in our calculations. We’ll also learn how to format the output displayed
to the user.
We call getting and displaying data this way as console I/O (“I/O”
is just short for input/output).
We’ll also learn how to use Python’s f-strings (short for formatted
string literals) to format output. For example, we can use f-strings with
format specifiers to display floating point numbers to a specific number
of digits to the right of the decimal place. With f-strings we can align
strings for displaying data in tabular format.
Á Warning
Learning objectives
• You will learn how to prompt the user for input, and handle input
from the user, converting it to an appropriate type if necessary.
• You will learn how to format strings using f-strings and interpola-
tion.
• You will learn how to use format specifiers within f-strings.
• You will write programs which receive user input, and produce
output based on that input, often performing calculations.
107
108 Console I/O
7.1 Motivation
It’s often the case that as we are writing code, we don’t have all the in-
formation we need for our program to produce the desired result. For
example, imagine you were asked to write a calculator program. No
doubt, such a program would be expected to add, subtract, multiply,
and divide. But what should it add? What should it multiply? You, as
the programmer, would not know in advance. Thus, you’d need a way to
get information from the user into the program.
Of course, there are many ways to get input from a user. The most
common, perhaps, is via a graphical user interface or GUI. Most, or
likely all, of the software you use on your laptop, desktop, tablet, or
phone makes use of a GUI.
In this chapter, we’ll see how to get input in the most simple way,
without having to construct a GUI. Here we’ll introduce getting user
input from the console. Later, in Chapter 13, we’ll learn how to read
data from an external file.
print(name)
print(quest)
Try it out. Copy this code, paste it into an editor window in your
text editor or IDE, and save the file as name_and_quest.py. Then run the
program. When run, the program first will prompt the user with ‘What
is your name? ’ and then it will assign the value returned by input() to
the variable name. Then it will prompt for the user’s quest and handle
the result similarly, assigning the result to the variable quest. Once the
program has gathered the information it needs, it prints the name and
quest that the user provided.
A session for this might look like this:
"""
Prompts the user for weight in kilograms
and converts to (US customary) pounds.
"""
POUNDS_PER_KILOGRAM = 2.204623
def kg_to_pounds(kg_):
return kg_ * POUNDS_PER_KILOGRAM
Converting strings to numeric types 111
If we save this code to file and try running it, it will fail when trying
to convert kilograms to pounds. We’ll get the message:
What happened? When the program gets input from the user:
the value returned from the input() function is a string. The value re-
turned from the input() function is always a string. Say the user enters
“82” at the prompt. Then what gets saved with the name kg is the string
'82' not the number 82 and we can’t multiply a string by a floating point
number—that makes no sense!
Happily, there’s an easy fix. Python provides built-in functions that
can be used to convert strings to numeric types (if possible). These are
the integer constructor, int(), and the float constructor, float(). These
functions can take a string which looks like it ought to be convertible
to a number, performs the conversion, and returns the corresponding
numeric type.
Let’s try these out in the Python shell:
>>> int('82')
82
>>> float('82')
82.0
The error message is telling us that the string '82.5' cannot be con-
verted to an int.
Now, returning to the problem at hand—converting user input to a
floating point number—here’s how we fix the code we started with.
112 Console I/O
"""
Prompts the user for weight in kilograms
and converts to (US customary) pounds.
"""
POUNDS_PER_KILOGRAM = 2.204623
def kg_to_pounds(kg_):
return kg_ * POUNDS_PER_KILOGRAM
Notice that we wrapped the call to input() within a call to the float
constructor. This expression is evaluated from the inside out (as you
might suspect). First the call to input() displays the prompt provided,
waits for input, then returns a string. The value returned (say, '82.5') is
then passed to the float constructor as an argument. The float construc-
tor does its work and the constructor returns a floating point number,
82.5. Now, when we pass this value to the function kg_to_pounds(), ev-
erything works just fine.
If you’ve seen mathematical functions before this is no different from
something like
𝑓(𝑔(𝑥))
where we would first calculate 𝑔(𝑥) and then apply 𝑓 to the result.
Enter an integer: 42
Enter another integer: 10
4210
Converting strings to numeric types 113
"""Prompt the user for two integers and display the sum."""
Be warned.
Additional resources
The documentation for any programming language can be a bit technical.
But it can’t hurt to take a look at the documentation for input(), int(),
and float(). If it makes your head spin, just navigate away and move on.
But maybe the documentation can deepen your understanding of these
functions. See relevant sections of:
• https://docs.python.org/3/library/functions.html
Some ways to format output 115
That’s fine, but perhaps we could make this more friendly. Let’s say we
wanted to print the result like this.
How would we go about it? There are a few ways. One somewhat clunky
approach would be to use string concatenation. Now, we cannot do this:
Why? Because we cannot concatenate floats and strings. One way to fix
this would be to explicitly convert the floating-point values to strings.
We can do this—explicitly—by using Python’s built-in function str().
Enter a number: 17
17.0 squared is 289.0
Again, this works, but it’s not a particularly elegant solution. If you were
to think “there must be a better way” you’d be right!
Here’s how we’d solve our earlier problem above using f-strings.
1
“F-string” is short for “formatted string literal”. These were introduced in
Python 3.6. For details, see the Input and Output section of the official Python
tutorial (https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-
literals), and PEP 498 (https://peps.python.org/pep-0498/) for a complete ratio-
nale behind literal string interpolation.
Format specifiers 117
>>> 1 / 3
0.3333333333333333
Now, in this example, it’s unlikely that you’d need or want to display
0.3333333333333333 to quite so many decimal places. Usually, it suffices
to print fewer digits to the right of the decimal point, e.g., 0.33 or 0.3333.
If we were to interpolate this value within an f-string, we’d get a similar
result.
>>> x = 1 / 3
>>> f'{x}'
'0.3333333333333333'
>>> x = 1 / 3
>>> f'{x:.2f}'
'0.33'
The syntax for this is to follow the interpolated element with a colon
: and then a specifier for the desired format. In the example above, we
used .2f meaning “display as a floating-point number to two decimal
places precision”. Notice what happens here:
>>> x = 2 / 3
>>> f'{x:.2f}'
'0.67'
Python has taken care of rounding for us, rounding the value
0.6666666666666666 to two decimal places. Unless you have very spe-
cific reasons not to do so, you should use f-strings for formatting output.
There are many format specifiers we can use. Here are a few:
>>> x = 2 / 3
>>> f'{x:.2%}'
'66.67%'
118 Console I/O
>>> x = 1234567890
>>> f'{x:,}'
'1,234,567,890'
>>> x = 1234567890
>>> f"{x:.4E}"
'1.2346E+09'
2
Sources: 2021 GDP from World Bank (https://data.worldbank.org/); 2021
population from the United Nations (https://www.un.org/development/desa/pd/).
Formatting tables 119
print(f'{"":<12}'
f'{"GDP":>16}'
f'{"Population":>16}'
f'{"GDP per":>16}')
print(f'{"Country":<12}'
f'{"($ billion)":>16}'
f'{"(million)":>16}'
f'{"capita ($)":>16}')
This would have been a little long as two single lines, so it’s been split
into multiple lines.3 This prints the column headings:
< is used for left-alignment (it points left). > is used for right-alignment
(it points right). The numbers in the format specifiers (above) designate
the width of the field (or column), so the country column is 12 characters
wide, GDP column is 16 characters wide, etc.
Now let’s see about a horizontal rule, dividing the column headings
from the data. For that we can use repeated concatenation.
So now we have
print(f'{"":<12}'
f'{"GDP":>16}'
f'{"Population":>16}'
f'{"GDP per":>16}')
print(f'{"Country":<12}'
f'{"($ billion)":>16}'
f'{"(million)":>16}'
f'{"capita ($)":>16}')
print('-' * 60)
which prints:
Now we need to handle the data. Let’s say we have the data in this
form:
3
This takes advantage of the fact that when Python sees two strings without an
operator between them it will concatenate them automatically. Don’t do this just to
save keystrokes. It’s best to reserve this feature for handling long lines or building
long strings across multiple lines.
120 Console I/O
gdp_chad = 11.780
gdp_chile = 317.059
gdp_china = 17734.063
gdp_colombia = 314.323
pop_chad = 16.818
pop_chile = 19.768
pop_china = 1412.600
pop_colombia = 51.049
(Yeah. This is a little clunky. We’ll learn better ways to handle data
later.) We could print the rows in our table like this:
print(f'{"Chad":<12}'
f'{gdp_chad:>16,.3f}'
f'{pop_chad:>16,.3f}'
f'{gdp_chad / pop_chad * 1000:>16,.0f}')
print(f'{"Chile":<12}'
f'{gdp_chile:>16,.3f}'
f'{pop_chile:>16,.3f}'
f'{gdp_chile / pop_chile * 1000:>16,.0f}')
print(f'{"China":<12}'
f'{gdp_china:>16,.3f}'
f'{pop_china:>16,.3f}'
f'{gdp_china / pop_china * 1000:>16,.0f}')
print(f'{"Colombia":<12}'
f'{gdp_colombia:>16,.3f}'
f'{pop_colombia:>16,.3f}'
f'{gdp_colombia / pop_colombia * 1000:>16,.0f}')
(Yeah. This is a little clunky too. We’ll see better ways soon.) Notice
that we can combine format specifiers, so for values in the GDP column
we have a format specifier
>16,.3f
The first symbol > indicates that the column should be right-aligned. The
16 indicates the width of the column. The , indicates that we should use
a comma as a thousands separator. .3f indicates formatting as a float-
ing point number, with three decimal places of precision. Other format
specifiers are similar.
Putting it all together we have:
gdp_chad = 11.780
gdp_chile = 317.059
gdp_china = 17734.063
gdp_colombia = 314.323
Example: currency converter 121
pop_chad = 16.818
pop_chile = 19.768
pop_china = 1412.600
pop_colombia = 51.049
print(f'{"":<12}'
f'{"GDP":>16}'
f'{"Population":>16}'
f'{"GDP per":>16}')
print(f'{"Country":<12}'
f'{"($ billion)":>16}'
f'{"(million)":>16}'
f'{"capita ($)":>16}')
print('-' * 60)
print(f'{"Chad":<12}'
f'{gdp_chad:>16,.3f}'
f'{pop_chad:>16,.3f}'
f'{gdp_chad / pop_chad * 1000:>16,.0f}')
print(f'{"Chile":<12}'
f'{gdp_chile:>16,.3f}'
f'{pop_chile:>16,.3f}'
f'{gdp_chile / pop_chile * 1000:>16,.0f}')
print(f'{"China":<12}'
f'{gdp_china:>16,.3f}'
f'{pop_china:>16,.3f}'
f'{gdp_china / pop_china * 1000:>16,.0f}')
print(f'{"Colombia":<12}'
f'{gdp_colombia:>16,.3f}'
f'{pop_colombia:>16,.3f}'
f'{gdp_colombia / pop_colombia * 1000:>16,.0f}')
which prints:
At this point, we don’t have all the tools we’d need to validate input
from the user, so for this program we’ll trust the user to be well-behaved
and to enter reasonable labels and rates. (We’ll see more on input vali-
dation and exception handling soon.) With this proviso, here’s how we
might start this program:
This code will prompt the user for source and target labels and then
print out the conversion the user has requested. Notice that we use an
f-string to interpolate the user-supplied labels.
4
For three-character ISO 4217 standard currency codes, see: https://en.wikiped
ia.org/wiki/ISO_4217.
Example: currency converter 123
source_amount = float(input(source_prompt))
exchange_rate = float(input(ex_rate_prompt))
See how choosing good names makes things so clear? You should aim
for similar clarity when naming objects and putting them to use.
The only thing left is to print the result. Again, we’ll use f-strings,
but this time we’ll include format specifiers to display the results to two
decimal places of precision.
"""
Currency Converter
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
5
From the Python documentation: Two or more physical lines may be joined
into logical lines using backslash characters (), as follows: when a physical line ends
in a backslash that is not part of a string literal or comment, it is joined with
the following forming a single logical line, deleting the backslash and the following
end-of-line character (https://docs.python.org/3/reference/lexical_analysis.html).
124 Console I/O
source_amount = float(input(source_prompt))
exchange_rate = float(input(ex_rate_prompt))
>>> x = 0.16251`
>>> f'{x:.1%}'
'16.3%'
>>> f'{x:.2f}'
'0.16'
>>> f'{x:>12}'
' 0.16251'
Exceptions 125
>>> f'{x:.3E}'
'1.625E-01'
7.12 Exceptions
ValueError
In an earlier chapter, we saw how trying to use math.sqrt() with a neg-
ative number as an argument results in a ValueError.
In this chapter we’ve seen another case of ValueError—where inputs
to numeric constructors, int() and float(), are invalid. These functions
take strings, so the issue isn’t the type of the argument. Rather, it’s an
issue with the value of the argument—some strings cannot be converted
to numeric types.
For example,
>>> int('5')
5
>>> int('5.0')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '5.0'
>>> int('kumquat')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'kumquat'
The first call, with the argument '5' succeeds because the string '5'
can be converted to an object of type int. The other two calls fail with
a ValueError.
int('5.0') fails because Python doesn’t know what to do about the
decimal point when trying to construct an int. Even though 5.0 has a
corresponding integer value, 5, Python rejects this input and raises a
ValueError.
The last example, int('kumquat'), fails for the obvious reason that
'kumquat' cannot be converted to an integer.
What about the float constructor, float()? Most numeric strings can
be converted to an object of type float. Examples:
126 Console I/O
>>> float('3.1415926')
3.1415926
>>> float('7')
7.0
>>> float('6.02E23')
6.02e+23
>>> float('+inf')
inf
>>> float('-inf')
-inf
>>> float('1,000')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: '1,000'
>>> float('pi')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: 'pi'
>>> float('one')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: 'one'
>>> float('toothache')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: 'toothache'
What about the string constructor, str()? It turns out that this can
never fail, since all objects in Python have a default string representation.
There’s no object type that can’t be turned into a string! Even things like
functions have a default string representation (though this representation
isn’t particularly human-friendly). Example:
Exercises 127
7.13 Exercises
Exercise 01
The following code has a bug. Instead of printing what the user types, it
prints <built-in function input>. What’s wrong and how would you fix
it?
Exercise 02
Which of the following can be converted to an int? (Try these out in a
Python shell.)
a. '1'
b. '1.0'
c. '-3'
d. 5
e. 'pumpkin'
f. '1,000'
g. '+1 802 555 1212'
h. '192.168.1.1'
i. '2023-01-15'
j. '2023/01/15'
k. 2023-01-15
l. 2023/01/15
Exercise 03
Which of the following can be converted to a float? (Try these out in a
Python shell.)
a. 3.141592
b. 'A'
c. '5.0 + 1.0'
d. '17'
e. 'inf' (Be sure to try this one out! What do you think it means?)
f. 2023/01/15
g. 2023/1/15
128 Console I/O
Exercise 04
Write a program which prompts the user for two floating-point numbers
and displays the product of the two numbers entered. Example:
Notice here that the second input is an integer string. It’s easy to
treat an input like this as an integer, just as easily as we could write
“17.0” instead of “17”.
>>> float(17)
17.0
Exercise 05
Write a program which prompts the user for their name and then prints
‘Hello’ followed by the user’s name. Example:
Learning objectives
• You will learn more about Booleans, how to construct a truth table,
and how to combine Booleans using the connectives and and or.
• You will learn how to write if, if/else, if/elif, and if/elif/else state-
ments in Python, which allow program execution to follow different
branches based on certain conditions.
• You will learn how to represent program flow and decisions using
flow charts and decision trees.
• You will learn a little about input validation.
• You will learn how to use convenient string methods such as
.upper(), .lower(), and .capitalize().
129
130 Branching and Boolean expressions
Boolean expressions
What is a Boolean expression? Well, true and false are both Boolean
expressions. We can build more complex expressions using the Boolean
connectives not, and, and or.
We often use truth tables to demonstrate. Here’s the simplest possible
truth table. We usually abbreviate true and false as T and F, respectively,
but here we’ll stick with the Python Boolean literals True and False.
1
There are some logicians who reject the law of the excluded middle. Consider
this proposition called the liar paradox or Epimenides paradox: “This statement
is false.” Is this true or false? If it’s true it’s false, if it’s false it’s true! Some take
this as an example of where the law of the excluded middle fails. Thankfully, we
don’t need to worry about this in this textbook, but if you’re curious, see: Law
of the excluded middle (Wikipedia). There’s even an episode in the original Star
Trek television series, in which Captain Kirk and Harry Mudd defeat a humanoid
robot by confronting it with the liar’s paradox. You can view it on YouTube: https:
//www.youtube.com/watch?v=QqCiw0wD44U.
Boolean logic and Boolean expressions 131
Now let’s see what happens with the other connectives, and and or.
Some languages have special symbols for these (e.g., Java uses && and ||
for and and or respectively). In Python, we simply use and and or. When
we use these connectives, we refer to the expressions being connected as
clauses.
So when using the conjunctive and, the expression is true if and only
if both clauses are true. In all other cases (above) the expression is false.
Here’s or.
You see, in the case of or, as long as one clause is true, the entire
expression is true. It is only when both clauses are false that the entire
expression is false.
We refer to clauses joined by and as a conjunction. We refer to clauses
joined by or as a disjunction. Let’s try this out in the Python shell:
>>> True
True
>>> False
False
>>> not True
False
>>> not False
True
>>> True and True
132 Branching and Boolean expressions
True
>>> True and False
False
>>> False and True
False
>>> False and False
False
>>> True or True
True
>>> True or False
True
>>> False or True
True
>>> False or False
False
Now, we don’t usually use literals like this in our code. Usually, we
want to test some condition to see if it evaluates to True or False. Then
our program does one thing if the condition is true and a different thing
if the condition is false. We’ll see how this works in another section.
De Morgan’s Laws
When working with Boolean expressions De Morgan’s Laws provide us
with handy rules for transforming Boolean expressions from one form
to another. Here we’ll use a and b to stand in for arbitrary Boolean
expressions.
Supplemental information
If you’d like to explore this further, here are some good resources:
>>> a = 12
>>> b = 31
>>> a == b
False
>>> a > b
False
>>> a < b
True
>>> a >= b
False
>>> a <= b
True
>>> a != b
True
>>> not (a == b)
True
Now what happens in the case of strings? Let’s try it and find out!
>>> a = 'duck'
>>> b = 'swan'
>>> a == b
False
>>> a > b
False
134 Branching and Boolean expressions
>>> a < b
True
>>> a >= b
False
>>> a <= b
True
>>> a != b
True
>>> not (a == b)
True
The situation is a little more complex than this, because strings can
have any character in them (not just letters, and hence the term “alpha-
betic order” loses its meaning). So what Python actually compares are
the code points of Unicode characters. Unicode is the system that Python
uses to encode character information, and Unicode includes many other
alphabets (Arabic, Armenian, Cyrillic, Greek, Hangul, Hebrew, Hindi,
Telugu, Thai, etc.), symbols from non-alphabetic languages such as Chi-
nese or Japanese Kanji, and many special symbols (®, €, ±, ∞, etc.).
Each character has a number associated with it called a code point (yes,
this is a bit of a simplification). In comparing strings, Python compares
these values.3
2
Yes, “aa” is a word, sometimes spelled “a’a”. It comes from the Hawai’ian, mean-
ing rough and jagged cooled lava (as opposed to pahoehoe, which is very smooth).
3
If you want to get really nosy about this, you can use the Python built-in
function ord() to get the numeric value associated with each character. E.g.,
>>> ord('A')
65
Branching 135
Thus, 'duck' < 'swan' evaluates to True, 'wing' < 'wings' evaluates
to True,and 'bingo' < 'bin' evaluates to False.
Now, you may wonder what happens in alphabetic systems, like En-
glish and modern European languages, which have majuscule (upper-
case) and miniscule (lower-case) letters (not all alphabetic systems have
this distinction).
8.3 Branching
Up until this point, all the programs we’ve seen and written proceed in
a linear fashion from beginning to end. This is fine for some programs,
but it’s rather inflexible. Sometimes we want our program to respond
differently to different conditions.
Imagine we wanted to write a program that calculates someone’s in-
come tax. The US Internal Revenue Service recognizes five different filing
statuses:
>>> ord('a')
97
>>> ord('b')
98
>>> ord('£')
163
See also: Joel Spolsky’s The Absolute Minimum Every Software Developer Ab-
solutely, Positively Must Know About Unicode and Character Sets (No Excuses!)
last seen in the wild at https://www.joelonsoftware.com/2003/10/08/the-absolute-
minimum-every-software-developer-absolutely-positively-must-know-about-unicode-
and-character-sets-no-excuses/
136 Branching and Boolean expressions
• single,
• married, filing jointly,
• married, filing separately,
• head of household,
• qualifying widow or widower with dependent child.4
So in writing our program we’d need to prompt the user for different
questions, gather different data, perform different calculations, and use
different tax tables depending on a user’s filing status. Obviously, this
cannot be done in a strictly linear fashion.
Instead, we’d want our program to be able to make decisions, and
follow different branches, depending on the results of those decisions.
This example of an income tax program is by no means unusual. In
fact, most real-world programs involve some kind of branching.
When our program includes branches, we execute different portions
of our program depending on certain conditions. Which conditions might
those be? It depends entirely on the program we wish to write.
Thus, most programming languages (Python included) allow for con-
trol flow—which includes branching and conditional execution of code.
How do we do this? In Python, we accomplish this with if, elif and
else statements (or combinations thereof).
Let’s think about what we’d need for this program to work:
• A secret word.
• Prompt the user for their guess.
• Then compare the user’s guess with the secret word.
• Print the appropriate message.
4
Discussion of the fairness or consequences of such a classification is outside the
scope of this text—a state of affairs that suits this author just fine.
if, elif, and else 137
"""
CS1210
Guess the secret word
"""
secret_word = "secret"
if user_guess == secret_word:
print("You WIN!")
else:
print("You LOSE!")
Here’s another example, but this time we’re using a nested if statement
and an elif (which is a portmanteau of “else” and “if”):
"""
CS 1210
What's for dinner?
"""
First we prompt the user for their bank balance. If this amount is less
than $20.00 then we prompt the user again to find out if they have any
meal points left. If they do, that is, if meal_points == 'y', we print “Eat
at the dining hall.” If not, we print “Eat that leftover burrito.”
Now, what happens if that very first condition is false? If that’s false,
we know we have more than $20.00, so our next comparison is:
Why not
you might ask? Because we only reach the elif if the first condition is
false. There’s no need to check again.
So if the bank balance is greater than or equal to $20.00 and less than
$50.00 we print “Order pizza from Leonardo’s”.
Now, what if the bank balance is greater than or equal to $50.00? We
print “Go out to Tiny Thai in Winooski with a friend!”.
We can have a single if statement, without elif or else. We can also,
as we’ve just seen, write compound statements which combine if and
else, if and elif, or all three, if, elif, and else. We refer to each block
of code in such compound statements as clauses (distinct from clauses
in a compound Boolean expression).
Supplemental resources
For more on control of flow, see: https://docs.python.org/3/tutorial/c
ontrolflow.html
if x % 2:
# it's odd
print(f"{x} is odd")
if not s:
print("The string, s, is empty!")
>>> bool(1)
True
>>> bool(-1)
True
>>> bool(0)
False
>>> bool('xyz')
True
>>> bool('')
False
>>> bool(None)
False
POUNDS_PER_KILOGRAM = 2.204623
Comprehension check
1. Can you use De Morgan’s Laws (see: Boolean expressions) to
rewrite the bounds checking above?
2. If we were to do this, would we be checking to see if fingers is in
the desired range or outside the desired range?
3. If your answer to 2 (above) was outside the desired range, how
would you need to modify the program?
>>> type('Mephistopheles')
<class 'str'>
n = 42
s = 'Cheese Shoppe'
creates an object of type str, and so on. The class definitions give Python
a blueprint for instantiating objects of these different types.
One of the things classes allow us to do is to define methods that are
part of the class definition and which are included with the objects along
with their data. Methods are nothing more than functions defined for a
class of objects which operate on the data of those objects.
Here’s an example. Let’s create a string object, s
>>> s = 'mephistopheles'
142 Branching and Boolean expressions
Here’s another method: upper() (you can guess what this does).
Now what if we had a string in all upper case, but wanted it in lower
case?
As you might imagine, these can come in handy, and there are many
more (take a peek at Built-in Types (https://docs.python.org/3/librar
y/stdtypes.html) and scroll down to String Methods if you’re curious).
It is important to keep in mind that these do not alter the string’s
value, they only return an altered copy of the string.
If you want to use the result returned by these methods you may need
to assign the result to a new object or overwrite the value of the current
variable, thus:
Some applications
Let’s say we wanted to write a program that prompted the user to see
if they wish to continue. At some point in our code, we might have
something like this:
What would happen if the user were to enter upper case ‘Y’? Clearly the
user intends to continue, but the comparison
response == 'y'
would return False and the program would abort. That might make for
an unhappy user.
We could write
or
This code (above) behaves the same whether the user enters ‘y’ or ‘Y’,
because we convert to lower case before performing the comparison. This
is one example of an application for string methods.
Another might be dealing with users that have the CAPS LOCK key
on.
path through or flow chart can follow one of two branches. If the con-
dition is true, we take one branch. If the condition is false, we take the
other branch.
A minimal example
Here’s a minimal example—a program which prompts a user for an inte-
ger, n, and then, if n is even, the program prints “n is even”, otherwise
the program prints “n is odd!”
In order to determine whether n is even or odd, we’ll perform a simple
test: We’ll calculate the remainder with respect to modulus two and
compare this value to zero.5 If the comparison yields True then we know
the remainder when dividing by two is zero and thus, n must be even.
If the comparison yields False then we know the remainder is one and
thus, n must be odd. (This assumes, of course, that the user has entered
a valid integer.)
Here’s what the flow chart for this program looks like:
5
Remember, if we have some integer 𝑛, then it must be the case that either 𝑛 ≡ 0
mod 2 or 𝑛 ≡ 1 mod 2. Those are the only possibilities.
146 Branching and Boolean expressions
"""
CS 1210
Even or odd?
"""
if n % 2 == 0:
print('n is even!')
else:
print('n is odd!')
Flow charts 147
if n % 2 == 0:
print('n is even!')
else:
print('n is odd!')
"""
CS 1210
Positive, negative, or zero?
Using nested if.
"""
if x > 0:
print('x is positive!')
else:
if x < 0:
print('x is negative!')
else:
print('x is zero!')
"""
CS 1210
Positive, negative, or zero?
Using elif.
"""
if x > 0:
print('x is positive!')
elif x < 0:
print('x is negative!')
else:
print('x is zero!')
Both programs—the one with the nested if and the one with elif—
correctly implement the program described by our flow chart. In some
cases, the choice is largely a matter of taste. In other cases, we may have
reason to prefer one or the other. All things being equal (in terms of
Decision trees 149
behavior), I think the elif solution presented here is the more elegant
of the two.
In any event, I hope you see that flow charts are a useful tool for
diagramming the desired behavior of a program. With a good flow chart,
implementation can be made easier. You should feel free to draw flow
charts of programs before you write your code. You may find that this
helps clarify your thinking and provides a plan of attack.
Here, we start on the left and move toward the right, making decisions
along the way. Notice that at each branching point we have two branches.
So, for example, to reach “watermelon, cantaloupe”, we make the
decisions: soft inside, small seeds, thick skin, and unsegmented. To reach
“peach”, we’d have to have made the decisions: soft inside, pit or stone,
and fuzzy.
6
If you’ve had a course in biology, you may have heard of a cladogram for
representing taxonomic relationships of organisms. A cladogram is a kind of decision
tree. If you’re curious, see: https://en.wikipedia.org/wiki/Cladogram and similar
applications.
150 Branching and Boolean expressions
"""
CS 1210
Decision tree for fruit identification
"""
else:
# hard inside
print("Walnut or almond")
Comprehension check
1. In the decision tree above, Figure 8.5, which decisions lead to plum?
(There are three.)
2. Revisit Section 8.4 and draw decision trees for the code examples
shown.
8.10 Exercises
Exercise 01
Evaluate the result of the following, given that we have:
a = True
b = False
c = True
Do these on paper first, then check your answers in the Python shell.
1. a or b and c
2. a and b or c
3. a and b and c
4. not a or not b or c
5. not (a and b)
Exercise 02
Evaluate the result of the following, given that we have:
a = 1
b = 'pencil'
c = 'pen'
d = 'crayon'
Do these on paper first, then check your answers in the Python shell.
Some of these may surprise you!
1. a == b
2. b > c
3. b > d or a < 5
4. a != c
5. d == 'rabbit'
6. c < d or b > d
7. a and b < d
8. (a == b) and (b != c)
9. (a and b) and (b < c)
152 Branching and Boolean expressions
Ask yourself, what does it mean for 'crayon' to be less than 'pencil'?
How would you interpret this? Ask yourself, what’s going on when an
expression like 0 or 'crayon' is evaluated?
Exercise 03
Complete the following if statements so that they print the correct mes-
sage. Notice that there are blank spaces in the code that you should
complete. You may assume we have three variables, with string values
assigned: cheese, blankets, toast, e.g.
cheese = 'runny'
2. Blankets are warm and toast is not pickled. Hint: use not or !=
if blankets :
print('Blankets are warm but toast is not pickled.')
if :
print('Toast is yummy and so is cheese!')
if :
print('Either toast is yummy or toast is green '
'(or maybe both).')
Exercises 153
Exercise 04
What is printed at the console for each of the following?
1.
>>> 'HELLO'.capitalize()
2.
>>> s = 'HoverCraft'
>>> s.lower()
3.
>>> s = 'wATer'.lower()
>>> s
Exercise 05
If we have only two possible outcomes in a decision tree, and decisions
are binary, then our tree has only one branching point. If we have four
possible outcomes, then our tree must have three branching points.
Learning objectives
• You will learn about incremental development, and how to use
comments as “scaffolding” for your code.
• You will learn how to organize and structure your code.
• You will understand how Python handles the main entry point of
your program, and how Python distinguishes between modules that
are imported and modules that are to be executed.
• You will be able to write code with functions that can be imported
and used independently of any driver code.
• You will understand how to test your code, and when to use asser-
tions in your code.
"""
A program which prompts the user for a radius of a circle,
r, and calculates and reports the circumference.
"""
import math
def circumference(r_):
return 2 * math.pi * r_
This is conventional and follows good coding style (e.g., PEP 8).
You may have seen something like this:
"""
A program which prompts the user for a radius of a circle,
r, and calculates and reports the circumference.
"""
import math
def circumference(r_):
return 2 * math.pi * r_
def main():
r = float(input('Enter a non-negative real number: '))
if r >= 0:
c = circumference(r)
print(f'The circumference of a circle of radius '
f'{r:,.3f} is {c:,.3f}.')
else:
print(f'I asked for a non-negative number, and '
f'{r} is negative!')
main()
While this is not syntactically incorrect, it’s not really the Python way
either.
main the Python way 157
Some textbooks use this, and there are abundant examples on the
internet, perhaps attempting to make Python code look more similar to
languages like C or Java (in the case of Java, an executable program
must implement main()). But again, this is not the Python way.
Here’s how things work in Python. Python has what is called the top-
level code environment. When a program is executed in this environment
(which is what happens when you run your code within your IDE or
from the command line), there’s a special variable __name__ which is
automatically set to the value '__main__'.1 '__main__' is the name of
the environment in which top-level code is run.
So if we wish to distinguish portions of our code which are auto-
matically run when executed (sometimes called driver code) from other
portions of our code (like imports and the functions we define), we do it
thus:
"""
A program which prompts the user for a radius of a circle,
r, and calculates and reports the circumference.
"""
import math
def circumference(r_):
return 2 * math.pi * r_
if __name__ == '__main__':
Let’s say we saved this file as circle.py. If we were to run this program
from our IDE or from the command line with
$ python circle.py
Python would read the file, would see that we’re executing it, and thus
would set __name__ equal to '__main__'. Then, after reading the definition
of the function circumference(r_), it would reach the if statement,
1
Some other programming languages refer to the top-level as the entry point.
'__main__' is the name of a Python program’s entry point.
158 Structure, development, and testing
if __name__ == '__main__':
This condition evaluates to True, and the code nested within this if
statement would be executed. So it would prompt the user for a radius,
and then check for valid input and return an appropriate response.
"""
tlce.py (top-level code environment)
Another program to demonstrate the significance
of __name__ and __main__.
"""
print(__name__)
if __name__ == '__main__':
print("Hello World!")
Copy this code and save it as tlce.py (short for top-level code environ-
ment). Then, try running this program from within your IDE or from
the command line. What will it print when you run it? It should print
__main__
Hello World!
So, you see, when we run a program in Python, Python sets the value
of the variable __name__ to the string '__main__', and then, when the
program performs the comparison __name__ == '__main__' this evaluates
to True, and the code within the if is executed.
"""
A program which imports tlce (from the previous example).
"""
import tlce
Save this as use_tlce.py and then run it. What is printed? This program
should print
main the Python way 159
tlce
So, if we import tlce then Python sets __name__ equal to 'tlce', and the
body of the if is never executed.
Why would we do this? One reason is that we can write functions
in one module, and import the module without executing any of the
module’s code, but make the functions available to us. Sound familiar?
It should. Consider what happens when we import the math module.
Nothing is executed, but now we have math.pi, math.sqrt(), math.sin(),
etc. available to us.
A complete example
Earlier we created a program which, given some radius, 𝑟, provided by the
user, calculated the circumference, diameter, surface area, and volume of
a sphere of radius 𝑟. Here it is, with some minor modifications, notably
the addition of the check on the value of __name__.
"""
Sphere calculator (sphere.py)
import math
def circumference(r_):
return 2 * math.pi * r_
def diameter(r_):
return 2 * r_
def surface_area(r_):
return 4 * math.pi * r_ ** 2
def volume(r_):
return 4 / 3 * math.pi * r_ ** 3
if __name__ == '__main__':
r = float(input("Enter a radius >= 0.0: "))
if r < 0:
print("Invalid input")
else:
print(f"The diameter is "
f"{diameter(r):0,.3f} units.")
print(f"The circumference is "
f"{circumference(r):0,.3f} units.")
print(f"The surface area is "
160 Structure, development, and testing
Now we have a program that prompts the user for some radius, 𝑟, and
uses some convenient functions to calculate these other values for a
sphere. But it’s not a stretch to see that we might want to use these
functions somewhere else!
Let’s say we’re manufacturing yoga balls—those inflatable balls that
people use for certain exercises requiring balance. We’d want to know
how much plastic we’d need to manufacture some number of balls. Say
our yoga balls are 33 centimeters in radius when inflated, and that we
want the thickness of the balls to be 0.1 centimeter.
In order to complete this calculation, we’ll need to calculate volume.
Why reinvent the wheel? We’ve already written a function to do this!
Let’s import sphere.py and use the function provided by this module.
"""
Yoga ball material requirements
"""
import sphere
# sphere.py must be in the same directory for this to work
RADIUS_CM = 33
THICKNESS_CM = 0.1
VINYL_G_PER_CC = 0.95
G_PER_KG = 1000
if __name__ == '__main__':
balls = int(input("How many balls do you want "
"to manufacture this month? "))
outer = sphere.volume(RADIUS_CM)
inner = sphere.volume(RADIUS_CM - THICKNESS_CM)
material_per_ball = outer - inner
total_material = balls * material_per_ball
total_material_by_weight
= total_material / VINYL_G_PER_CC / G_PER_KG
See? We’ve imported sphere so we can use its functions. When we import
sphere, __name__ (for sphere) takes on the value sphere so the code under
if __name__ == '__main__' isn’t executed!
This allows us to have our cake (a program that calculates diameter,
circumference, surface area, and volume of a sphere) and eat it too (by
Program structure 161
1. docstring
2. imports (if any)
3. constants (if any)
4. function definitions (if any)
"""
A docstring, delimited by triple double-quotes,
which includes your name and a brief description
of your program.
"""
def g(x_):
return (x_ - 1) ** 2
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
That’s a start, but then you remember that Python’s input() function
returns a string, and thus you need to convert these strings to floats.
You decide that before you start writing code you’ll add this to your
comments, so you don’t forget.
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
2
Here we’ve excluded if __name__ == __main__: to avoid clutter in presentation.
Iterative and incremental development 163
Now you decide you’re ready to start coding, so you start with step
1.
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
Notice the comments are left intact and there’s a print statement added
to verify mass is correctly stored in mass. Now you run your code—yes,
it’s incomplete, but you decide to run it to confirm that the first step is
correctly implemented.
So that works as expected. Now you decide you can move on to step 2.
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
Again, so far so good. Now it’s time to perform the calculation of kinetic
energy.
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
At this point, you decide that getting the input is working OK, so
you remove the print statements following mass and velocity. Then you
decide to focus on printing a pretty result. You know you want to use
format specifiers, but you don’t want to fuss with that quite yet, so you
start with something simple (but not very pretty).
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
You realize that you typed energy when you should have used
kinetic_energy. That’s not hard to fix, and since you know the other
code is working OK you don’t need to touch it.
Here’s the fix:
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
The last step is to add format specifiers for pretty printing, but since
everything else is working OK, the only thing you need to focus on are
the format specifiers. Everything else is working!
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
You decide that’s OK, but you’d rather have comma separators in
your output, so you modify the format specifiers.
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
Looks great!
Now we can remove the comments we used as scaffolding, and we
finish with:
"""
Kinetic Energy Calculator
Egbert Porcupine <egbert.porcupine@uvm.edu>
CS 1210
"""
If your code has multiple branches, it’s probably a good idea to test
each branch. Obviously, with larger programs this could get unwieldy,
but for small programs with few branches, it’s not unreasonable to try
each branch.
Some examples
Let’s say we had written a program that is intended to take pressure in
pounds per square inch (psi) and convert this to bars. A bar is a unit of
pressure and 1 bar is equivalent to 14.503773773 psi.
Without looking at the code, let’s test our program. Here are some
values that represent reasonable inputs to the program.
Oh dear! Already we have a problem. Looking at the last line of the error
message we see
How could this have happened? Surely if pressure in psi is zero, then
pressure in bars should also be zero (as in a perfect vacuum).
When we look at the code (you can see the offending line in the
traceback above), we see that instead of taking the value in psi and
dividing by the number of psi per bar, we’ve got our operands in the
wrong order. Clearly we need to divide psi by psi per bar to get the
correct result. Again you can see from the traceback, above, that there’s
a constant PSI_PER_BAR, so we’ll just reverse the operands. This has the
added benefit of having a non-zero constant in the denominator, so after
this change, this operation can never result in a ZeroDivisionError ever
again.
Now let’s try it again.
Brilliant.
Let’s try a different value. How about 100? You can see in the table
above that 100 psi is approximately equivalent to ~6.894757293 bars.
This looks correct, though we can see now that we’re displaying more
digits to the right of the decimal point than are useful.
Let’s say we went back to our code and added format specifiers to
that both psi and bars are displayed to four decimal places of precision.
170 Structure, development, and testing
All our observed, actual outputs agree with our expected outputs.
What about negative values for pressure? Yes, there are cases where a
negative pressure value makes sense. Take, for example, an isolation room
for biomedical research. The air pressure in the isolation room should be
lower than pressure in the outside hallways or adjoining rooms. In this
way, when the door to an isolation room is opened, air will flow into the
room, not out of it. This helps prevent contamination of uncontrolled
outside environments. It’s common to express the difference in pressure
between the isolation room and the outside hallway as a negative value.
Does our program handle such values? Let’s expand our table:
Let’s test:
This one passes, but what about the next one (with the string contain-
ing the comma)? Will the conversion of the string '15,750' (notice the
comma) be converted correctly to a float? Alas, this fails:
Later, we’ll learn how to create a modified copy of such a string with
the commas removed, but for now let it suffice to say this can be fixed.
Notice however, that if we hadn’t checked this large value, which could
reasonably be entered by a human user with the comma as shown, we
might not have realized that this defect in our code existed! Always test
with as many ways the user might enter data as you can think of!
With that fix in place, all is well.
Splendid.
This prompts another thought: what if the user entered psi in scien-
tific notation like 1E3 for 1,000? It turns out that the float constructor
handles inputs like this—but it never hurts to check!
Notice that by testing, we’ve been able to learn quite a bit about our
code without actually reading the code! In fact, it’s often the case that
the job of writing tests for code falls to developers who aren’t the ones
172 Structure, development, and testing
writing the code that’s being tested! One team of developers writes the
code, a different team writes the tests for the code.
The important things we’ve learned here are:
Keep checking…
Oops! This is the same problem we saw earlier: the float constructor
doesn’t handle numeric strings containing commas. Let’s assume we’ve
applied a similar fix and then test again.
Yay! Success!
Now, what happens if we were to test with negative values for either
grams or atomic weight?
Nonsense! Ideally, our program should not accept negative values for
grams, and should not accept negative values or zero for atomic weight.
In any event, you see now how useful testing a range of values can be.
Don’t let yourself be fooled into thinking your program is defect-free if
you’ve not tested it with a sufficient variety of inputs.
174 Structure, development, and testing
Figure 9.1: Grace Murray Hopper. Source: The Grace Murray Hop-
per Collection, Archives Center, National Museum of American History
(image is in the public domain)
After reviewing the code and finding no error, she investigated further
and found a moth in one of the computer’s relays (remember this was
back in the days when a computer filled an entire large room). The moth
was removed, and taped into Hopper’s lab notebook.
4
Judging from Hopper’s notebook (9 September 1947), the misbehaving program
was a “multi-adder test”. It appears they were running the machine through a se-
quence of tests—e.g., tests for certain trigonometric functions took place earlier that
day. At least one had failed and some relays (hardware components) were replaced.
The multi-adder test was started at 3:25 PM (Hopper uses military time in the note-
book: “1525”), and twenty minutes later, the moth was taped into the notebook.
It’s not clear how the problem became manifest, but someone went looking at the
hardware and found the moth.
The origin of the term “bug” 175
Figure 9.2: A page from Hopper’s notebook containing the first “bug”.
Source: US Naval Historical Center Online Library (image is in public
domain)
sales_tax = calc_sales_tax(items)
assert sales_tax >= 0
if sales_tax < 0:
raise AssertionError
return math.sqrt(a ** 2 + b ** 2)
What’s going on here? This isn’t data validation. Rather, we’re docu-
menting conditions that must hold for the function to return a valid
result, and we ensure that the program will fail if these conditions aren’t
met. We could have a degenerate triangle, where one or both legs have
length zero, but it cannot be the case that either leg has negative length.
This approach has the added benefit of reminding the programmer what
conditions must hold in order to ensure correct behavior.
Judicious use of assertions can help you write correct, robust code.
Some caveats
It’s important to understand that assert is a Python keyword and not
the name of a built-in function. This is correct:
as what is being asserted. But non-empty tuples are truthy, and so this
will never result in an AssertionError, no matter what the value of x!
Let’s test it
178 Structure, development, and testing
>>> x = -42
>>> assert(0.0 <= x <= 1.0, "x must be in [0.0, 1.0]")
<stdin>:1: SyntaxWarning: assertion is always true,
perhaps remove parentheses?
>>>
>>> x = -42
>>> assert 0.0 <= x <= 1.0, "x must be in [0.0, 1.0]"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: x must be in [0.0, 1.0]
>>>
>>> x = 0.42
>>> assert 0.0 <= x <= 1.0, "x must be in [0.0, 1.0]"
>>>
(Notice the >>> at the end of the snippet above, indicating that the
assertion has passed and is thus silent.)
You should try adding assertions to your code. In fact, the NASA/JPL
Laboratory for Reliable Software published a set of guidelines for produc-
ing reliable code, and one of these is “Use a minimum of two runtime
assertions per function.”6
9.7 Rubberducking
“Rubberducking”? What on Earth is “rubberducking”? Don’t laugh: rub-
berducking is one of the most powerful debugging tools in the known
universe! Many programmers keep a little rubber duck handy on their
desk, in case of debugging emergencies.
Here’s how it works. If you get stuck, and cannot solve a particular
problem or cannot fix a pesky bug you talk to the duck. Now, rubber
ducks aren’t terribly sophisticated, so you have to explain things to them
in the simplest possible terms. Explain your problem to the duck using
as little computer jargon as you can. Talk to your duck as if it were an
intelligent five-year-old. You’d be amazed at how many problems can be
solved this way!
Why does it work?
First, by talking to your duck, you step outside your code for a while.
You’re talking about your code without having to type at the keyboard,
and without getting bogged down in the details of syntax. You’re talking
about what you think your code should be doing.
Second, your duck will never judge you. It will remain silent while
you do your best to explain. Ducks are amazing listeners!
6
G.J. Holzmann, 2006, “The Power of 10: Rules for Developing Safety-Critical
Code”, IEEE Computer, 39(6). doi:10.1109/MC.2006.212.
Exceptions 179
It’s very often the case that while you’re explaining your troubles to
the duck, or describing what you think your code should be doing, that
you reach a moment of realization. By talking through the problem you
arrive at a solution or you recognize where you went wrong.
9.8 Exceptions
AssertionError
As we’ve seen, if an assertion passes, code execution continues normally.
However, if an assertion fails, an AssertionError is raised. This indicates
that what has been asserted has evaluated to False.
If you write an assertion, and when you test your code an
AssertionError is raised, then you should do two things:
1. Make sure that the assertion you’ve written is correct. That is, you
are asserting some condition is true when it should, in fact, be true.
2. If you’ve verified that your assertion statement(s) are correct, and
an AssertionError continues to be raised, then it’s time to debug
your code. Continue updating and testing until the issue is resolved.
9.9 Exercises
Exercise 01
Arrange the following code and add any missing elements so that it
follows the stated guidelines for program structure (as per section 9.2):
def square(x_):
return x * x
x_sqrd = square(x)
print(f"{x} squared is {x_sqrd}."
180 Structure, development, and testing
Exercise 02
Write a complete program which prompts the user for two integers (one
at a time) and then prints the sum of the integers. Be sure to follow the
stated guidelines for program structure.
Exercise 03
Egbert has written a function which takes two arguments, both represent-
ing angles in degrees. The function returns the sum of the two degrees,
modulo 360.
Here’s an example of one test of this function:
What other values might you use to test such a function? For each
pair of values you choose, give the expected output of the function. (See
section 9.3)
Exercise 04
Consider this module (program):
"""
A simple program
"""
def cube(x_):
return x_ ** 3
assert cube(3) == 27
assert cube(0) == 0
assert cube(-1) == -1
Exercise 05
What’s wrong with these assertions and how would you fix them?
a.
b.
Exercise 06
Write a program with a function that takes three integers as arguments
and returns their sum. Comment first, then write your code.
Chapter 10
Sequences
In this chapter, we’ll introduce two new types, lists and tuples. These
are fundamental data structures that can be used to store, retrieve, and,
in some contexts, manipulate data. We sometimes refer to these as “se-
quences” since they carry with them the concepts of order and sequence.
Strings (str) are also sequences.
Learning objectives
• You will learn about lists, and about common list operations, in-
cluding adding and removing elements, finding the number of el-
ements in a list, checking to see if a list contains a certain value,
etc.
• You will learn that lists are mutable. This means that you can
modify a list after it’s created. We can append items, remove items,
change the values of individual items and more.
• You will learn about tuples. Tuples are unlike lists in that they are
immutable—they cannot be changed.
• You will learn that strings are sequences.
• You will learn how to use indices to retrieve individual values from
these structures.
In the next chapter, we’ll learn how to iterate over the elements of a
sequence.
Terms introduced
• list
• tuple
• mutable
• immutable
• index
• sequence unpacking
• slicing
183
184 Sequences
10.1 Lists
The list is one of the most widely used data structures in Python. One
could not enumerate all possible applications of lists.1 Lists are ubiqui-
tous, and you’ll see they come in handy!
What is a list?
A list is a mutable sequence of objects. That sounds like a mouthful, but
it’s not that complicated. If something is mutable, that means that it can
change (as opposed to immutable, which means it cannot). A sequence is
an ordered collection—that is, each element in the collection has its own
place in some ordering.
For example, we might represent customers queued up in a coffee shop
with a list. The list can change—new people can get in the coffee shop
queue, and the people at the front of the queue are served and they
leave. So the queue at the coffee shop is mutable. It’s also ordered—each
customer has a place in the queue, and we could assign a number to each
position. This is known as an index.
There are other ways of creating lists in Python, but this will suffice for
now.
At the Python shell, we can display a list by giving its name.
1
If you’ve programmed in another language before, you may have come to know
similar data structures, e.g., ArrayList in Java, mutable vectors in C++, etc. How-
ever, there are many differences, so keep that in mind.
Lists 185
>>> aint_nothing_here = []
>>> aint_nothing_here
[]
>>> data = [4.2, 9.5, 1.1, 3.1, 2.9, 8.5, 7.2, 3.5, 1.4, 1.9, 3.3]
Now let’s access individual elements of the list. For this, we give the
name of the list followed immediately by the index enclosed in brackets:
>>> data[0]
4.2
The element in the list data, at index 0, has a value of 4.2. We can access
other elements similarly.
2
Some languages are one-indexed, meaning that their indices start at one, but
these are in the minority. One-indexed languages include Cobol, Fortran, Julia,
Matlab, R, and Lua.
186 Sequences
>>> data[1]
9.5
>>> data[9]
1.9
IndexError
Let’s say we have a list with 𝑛 elements. What happens if we try to access
a list using an index that doesn’t exist, say index 𝑛 or index 𝑛 + 1?
>>> data
[4.2, 9.5, 1.1, 3.1, 2.9, 8.5, 7.2, 3.5, 1.4, 1.9, 3.3]
>>> data[7] = 6.1
>>> data
[4.2, 9.5, 1.1, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> sum(data)
52.8
>>> len(data)
11
>>> max(data)
9.5
>>> min(data)
1.4
>>> data
[4.2, 9.5, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> data.append(5.9)
>>> data
[4.2, 9.5, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3, 5.9]
By using the .append() method, we’ve appended the value 5.9 to the end
of the list.
>>> data.pop()
5.9
>>> data
[4.2, 9.5, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
Notice that the value 5.9 is returned, and that the last element in the
list (5.9) has been removed.
Sometimes we wish to pop an element from a list that doesn’t happen
to be the last element in the list. For this we can supply an index, .pop(i),
where i is the index of the element we wish to pop.
>>> data.pop(1)
9.5
Lists 189
>>> data
[4.2, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
For reasons which may be obvious, we cannot .pop() from an empty list,
and we cannot .pop(i) if the index i does not exist.
>>> data
[4.2, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> data.sort()
>>> data
[1.4, 1.9, 2.9, 3.1, 3.3, 4.2, 4.7, 6.1, 7.2, 8.5]
This is unlike the string methods like .lower() which return an altered
copy of the string. Why is this? Strings are immutable; lists are mutable.
Because .sort() sorts a list in place, it returns None. So don’t think
you can work with the return value of .sort() because there isn’t any!
Example:
>>> m = [5, 7, 1, 3, 8, 2]
>>> n = m.sort()
>>> n
>>> type(n)
<class 'NoneType'>
>>> data = [4.2, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> copy_of_data = data # naively thinking you're making a copy
>>> data.sort()
>>> data
[1.4, 1.9, 2.9, 3.1, 3.3, 4.2, 4.7, 6.1, 7.2, 8.5]
>>> copy_of_data
[1.4, 1.9, 2.9, 3.1, 3.3, 4.2, 4.7, 6.1, 7.2, 8.5]
>>> data = [4.2, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> copy_of_data = data.copy() # call the copy method
>>> data.sort()
>>> data
[1.4, 1.9, 2.9, 3.1, 3.3, 4.2, 4.7, 6.1, 7.2, 8.5]
>>> copy_of_data
[4.2, 4.7, 3.1, 2.9, 8.5, 7.2, 6.1, 1.4, 1.9, 3.3]
>>> x[len(x) - 1]
Let’s unpack that. Within the brackets we have the expression len(x)
- 1. len(x) returns the number of elements in the list, and then we
subtract 1 to adjust for zero-indexing (if we have 𝑛 elements in a list,
the index of the last element is 𝑛 − 1). So that works, but it’s a little
clunky. Fortunately, Python allows us to get the last element of a list
with an index of -1.
>>> x[-1]
You may think of this as counting backward through the indices of the
list.
A puzzle (optional)
Say we have some list x (as above), and we’re intrigued by this idea of
counting backward through a list, and we want to find an alternative way
to access the first element of any list of any size with a negative-valued
3
The reasons for this state of affairs is beyond the scope of this text. However,
if you’re curious, see: https://docs.python.org/3/library/copy.html.
4
There are other approaches to creating a copy of a list, specifically using the
list constructor or slicing with [:], but we’ll leave these for another time. However,
slicing is slower than the other two. Source: I timed it.
5
Actually it makes what’s called a shallow copy. See: https://docs.python.org/
3/library/copy.html.
Tuples 191
index. Is this possible? Can you write a solution that works for any list
x?
10.2 Tuples
A tuple? What’s a tuple? A tuple is an immutable sequence of objects.
Like lists they allow for indexed access of elements. Like lists they
may contain any arbitrary type of Python object (int, float, bool, str,
etc.). Unlike lists they are immutable, meaning that once created they
cannot change. You’ll see that this property can be desirable in certain
contexts.
>>> singleton = 5,
>>> singleton
(5,)
>>> singleton = ('Hovercraft',)
>>> singleton
('Hovercraft',)
>>> (5)
5
>>> ('Hovercraft')
'Hovercraft'
>>> empty = ()
>>> empty
()
>>> t[-1]
-1.0
tuples will be faster. However, the difference is small, and could only
become a factor if handling many records (think millions).
>>> t = (1, 2, 3)
>>> t[0]
1
>>> t[0] = 51
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
There you have it: “ ‘tuple’ object does not support item assignment.”
>>> t = (1, 2, 3)
>>> t = ('a', 'b', 'c')
“There!” you say, “I’ve changed the tuple!” No, you haven’t. What’s
happened here is that you’ve created a new tuple, and given it the same
name t.
>>> t[0][0] = 5
>>> t
([5, 2, 3],)
Haven’t we just modified the tuple? Actually, no. The tuple contains
the list (which is mutable). So we can modify the list within the tuple,
but we can’t replace the list with another list.
194 Sequences
However, lists are mutable. Later, we’ll see another mutable data
structure, the dictionary.
Immutable objects
You may ask what’s been happening in cases like this:
>>> x = 75021
>>> x
75021
>>> x = 61995
>>> x
61995
>>> x = 75021
>>> id(x)
4386586928
>>> x = 61995
>>> id(x)
4386586960
>>> s = 'Pharoah Sanders' # who passed away the day I wrote this
>>> id(s)
4412581232
>>> s = 'Sal Nistico'
>>> id(s)
4412574640
Mutable objects
Now let’s see what happens in the case of a list. Lists are mutable.
6
While using id() is fine for tinkering around in the Python shell, this is the
only place it should be used. Never include id() in any programs you write. The
Python documentation states that id() returns “the ‘identity’ of an object. This is
an integer which is guaranteed to be unique and constant for this object during its
lifetime. Two objects with non-overlapping lifetimes may have the same id() value.”
So please keep this in mind.
196 Sequences
See? We make changes to the list and the ID remains unchanged. It’s
the same object throughout!
>>> lst_a.append(6)
>>> lst_b
[1, 2, 3, 4, 5, 6]
See? lst_b isn’t a copy of lst_a, it’s a different name for the same object!
If a mutable value has more than one name, if we affect some change
in the value via one name, all the other names still refer to the mutated
value.
Now, what do you think about this example:
Are lst_a and lst_b different names for the same object? Or do they
refer to different objects?
Mutability and immutability 197
>>> lst_a
[1, 2, 3, 4, 5, 6]
>>> lst_b
[1, 2, 3, 4, 5]
lst_a and lst_b are names for different objects! Now, does this mean
that assignment works differently for mutable and immutable objects?
Not at all.
Then why, you may ask, when we assign 1 to x and 1 to y do both
names refer to the same value, whereas when we assign [1, 2, 3, 4, 5]
to lst_a and [1, 2, 3, 4, 5] to lst_b we have two different lists?
Let’s say you and a friend wrote down lists of the three greatest base-
ball teams of all time. Furthermore, let’s say your lists were identical…
Trigger warning: opinions about MLB teams follow!
Now, my list is my list, and Jane’s list is Jane’s list. These are two
different lists.
Let’s say that the Dodgers fell out of favor with Jane, and she replaced
them with the Cardinals (abhorrent, yes, I know).
>>> janes_list.pop()
'Dodgers'
>>> janes_list.append('Cardinals')
>>> janes_list
['Cubs', 'Tigers', 'Cardinals']
>>> my_list
['Cubs', 'Tigers', 'Dodgers']
That makes sense, right? Even though the lists started with identical ele-
ments, they’re still two different lists and mutating one does not mutate
the other.
But be aware that we can give two different names to the same mu-
table object (as shown above).
modifying a copy of a list, when we’re actually modifying the list under
another name!
Different texts may use different starting indices. For example, a linear
algebra text probably starts indices at one. A text on set theory is likely
to use indices starting at zero.
In Python, sequences—lists, tuples, and strings—are indexed in this
fashion. All Python indices start at zero, and we refer to Python as being
zero indexed.
Indexing works the same for lists, tuples, and even strings. Remember
that these are sequences—ordered collections—so each element has an
index, and we may access elements within the sequence by its index.
my_list = ['P', 'O', 'R', 'C', 'U', 'P', 'I', 'N', 'E']
We start indices at zero, and for a list of length 𝑛, the indices range from
zero to 𝑛 − 1.
It’s exactly the same for tuples.
my_tuple = ('P', 'O', 'R', 'C', 'U', 'P', 'I', 'N', 'E')
Concatenating lists and tuples 199
The picture looks the same, doesn’t it? That’s because it is! It’s even the
same for strings.
my_string = 'PORCUPINE'
or
This works just like coupling railroad cars. Coupling two trains with
multiple cars preserves the ordering of the cars.
Answering the inevitable question: Can we concatenate a list with a
tuple using the + operator? No, we cannot.
However, there are times when we really mean to make a copy. Earlier
we saw that the .copy() method returns a shallow copy of a list. We’ve
also seen that we can copy a list using a slice.
or
There’s another way we can copy a list: using the list constructor. The
list constructor takes some iterable and iterates it, producing a new list
composed of the elements yielded by iteration. Since lists are iterable,
we can use this to create a copy of a list.
Fun fact: Under the hood, .copy() simply calls the list constructor to
make a new list.
Finding an element within a sequence 201
We can check to see if an element exists using the Python keyword in.
or
or
202 Sequences
So, we can see that the Python keyword in can come in very handy
in a variety of ways.
However, this one can bite. If the element is not in the list, Python will
raise a ValueError exception.
>>> fruits.index('frog')
Traceback (most recent call last):
File "/Library/Frameworks/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
ValueError: 'frog' is not in list
This is rather inconvenient, since if this were to occur when running your
program, it would crash your program! Yikes! So what can be done?
Later on in this textbook we’ll learn about exception handling, but for
now, here’s a different solution: just check to see if the element is in the
list (or other sequence) first by using an if statement, and then get the
index if it is indeed in the list.
>>> x = (2,)
>>> x
2
>>> x, y, z = ('a', 'b', 'c')
>>> x
'a'
>>> y
'b'
>>> z
'c'
However, the number of variables on the left-hand side must match the
number of elements in the tuple on the right-hand side. If they don’t
match, we get an error, either ValueError: too many values to unpack
or ValueError: not enough values to unpack.
Tuple unpacking is particularly useful by:
x, y = [1, 2]
But this isn’t used as much as tuple unpacking. Can you think why this
may be so?
The reason is that lists are dynamic, and we may not know at runtime
how many elements we have to unpack. This scenario occurs less often
204 Sequences
with tuples, since they are immutable, and once created, we know how
many elements they have.
or
This makes it clear visually that we’re only concerned with a specific
value, and is preferred over names like temp, foo, junk or whatever.
Occasionally, you may see code where two elements of an unpacked
sequence are ignored. In these cases, it’s not unusual to see both _ and
__ used as variable names to signify “I don’t care.”
Examples:
or
or
In this instance, Python unpacks the first element of the tuple to _, then
it unpacks the second element of the tuple to _, and then it unpacks the
third element to the variable elevation.
If you were to inspect the value of _ after executing the line above,
you’d see it holds the value −72.8851266.
Strings are sequences 205
int a = 1
int b = 2
// now swap
int temp = a
a = b
b = temp
a = 1
b = 2
# now swap
b, a = a, b
We can use min() and max() with strings. When we do, Python will
compare characters (Unicode code points) within the string. In the case
of min(), Python will return the character with the lowest-valued code
point. In the case of max(), Python will return the character with the
highest-valued code point.
We can also use len() with strings. This returns the length of the
string.
Comprehension check
1. What is returned by max('headroom')?
2. What is returned by min('frequency')?
3. What is returned by len('toast')?
Built-ins
Methods
10.11 Slicing
Python supports a powerful means for extracting data from a sequence
(string, list or tuple) called slicing.
Basic slicing
We can take a slice through some sequence by specifying a range of
indices.
208 Sequences
>>> un_security_council[0:5]
['China', 'France', 'Russia', 'UK', 'USA']
“Hey! Wait a minute!” you say, “We provided a range of six indices! Why
doesn’t this include ‘Albania’ too?”
Reasonable question. Python treats the ending index as its stopping
point, so it slices from index 0 to index 5 but not including the element
at index 5! This is the Python way, as you’ll see with other examples
soon. It does take a little getting used to, but when you see this kind of
indexing at work elsewhere, you’ll understand the rationale.
What if we wanted the non-permanent members whose term ends in
2023? That’s Albania, Brazil, Gabon, Ghana, and UAE.
To get that slice we’d use
>>> un_security_council[5:10]
['Albania', 'Brazil', 'Gabon', 'Ghana', 'UAE']
Again, Python doesn’t return the item at index 10; it just goes up to
index 10 and stops.
Some shortcuts
Python allows a few shortcuts. For example, we can leave out the starting
index, and Python reads from the start of the list (or tuple).
>>> un_security_council[:10]
['China', 'France', 'Russia', 'UK', 'USA',
'Albania', 'Brazil', 'Gabon', 'Ghana', 'UAE']
By the same token, if we leave out the ending index, then Python will
read to the end of the list (or tuple).
>>> un_security_council[10:]
['India', 'Ireland', 'Kenya', 'Mexico', 'Norway']
Now you should be able to guess what happens if we leave out both start
and end indices.
Slicing 209
>>> un_security_council[:]
['China', 'France', 'Russia', 'UK', 'USA',
'Albania', 'Brazil', 'Gabon', 'Ghana', 'UAE',
'India', 'Ireland', 'Kenya', 'Mexico', 'Norway']
• un_security_council[-1:]
• un_security_council[:-1]
• un_security_council[5:0]
• un_security_council[5:-1]
What happens if the stride is greater than the number of elements in the
sequence?
>>> un_security_council[::1000]
['China']
>>> un_security_council[-1::-1]
['Norway', 'Mexico', 'Kenya', 'Ireland', 'India',
'UAE', 'Ghana', 'Gabon', 'Brazil', 'Albania',
'USA', 'UK', 'Russia', 'France', 'China']
Now you know one way to get the reverse of a sequence. Can you think
of some use cases for changing the stride?
210 Sequences
All we’ve done is give two different names to the same list, by assign-
ment. In the example with the function foo (above) the only difference is
that the name lst exists only within the scope of the function. Otherwise,
these two examples behave the same.
Notice that there is no “reference” being passed, just an assignment
taking place.
Names have scope but no type. Values have type but no scope.
7
If you search on the internet you may find sources that say that immutable
object are passed by value and mutable objects are passed by reference in Python.
This is not correct! Python always passes by assignment—no exceptions. This is
different from many other languages (e.g., C, C++, Java). If you haven’t heard
these terms before, just ignore them.
Passing mutables to functions 211
What do we mean by that? Well, in the case where we pass the list
breakfast to the function foo, we create a new name for breakfast, lst.
This name, lst, exists only within the scope of the function, but the value
persists. Since we’ve given this list another name, breakfast, which exists
outside the scope of the function we can still access this list once the
function call has returned, even though the name lst no longer exists.
Here’s another demonstration which may help make this more clear.
However, now we can no longer use the name lst since it exists only
within the scope of the function.
>>> lst
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'lst' is not defined. Did you mean: 'list'?
10.13 Exceptions
IndexError
When dealing with sequences, you may encounter IndexError. This ex-
ception is raised when an integer is supplied as an index, but there is no
element at that index.
Notice that the error message explicitly states “list index out of range”.
In the example above, we have a list of six elements, so valid indices range
up to five. There is no element at index six, so if we attempt to access
lst[6], an IndexError is raised.
TypeError
Again, when dealing with sequences, you may encounter TypeError in
a new context. This occurs if you try to use something other than an
integer (or slice) as an index.
>>> lst[1.0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers or slices, not float
>>> lst['cheese']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers or slices, not str
>>> lst[None]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: list indices must be integers or slices, not NoneType
Notice that the error message explicitly states that “list indices must
be integers or slices” and in each case, it identifies the offending type
that was actually supplied.
Exercises 213
10.14 Exercises
Where appropriate, guess first, then check your guess in the Python shell.
Exercise 01
Given the following list,
Exercise 02
Given the following tuple
Exercise 03
Given the polynomial 4𝑥3 + 2𝑥2 + 5𝑥 − 4, write the coefficients as a tuple
and name the result coefficients. Use an assignment.
a. What is the value of len(coefficients)?
Exercise 04
Given the lists in exercises 1 and 2
Exercise 05
Write a function which, given any arbitrary list, returns True if the list
has an even number of elements and False if the list has an odd number
of elements. Name your function is_even_length().
Exercise 06
Given the following list
write one line of code which calls the function is_even_length() (see
Exercise 05) with moons as an argument and assigns the result to an
object named n. What is n’s type?
Exercise 07
Given the following list, which contains data about some moons of Saturn
and their diameters (in km),
e. Write one line of code which adds Iapetus to the list, using the
same structure. The diameter of Iapetus is 1468.6 (km).
Exercises 215
Exercise 08
Given the following
Exercise 09
There are three ways to make a copy of a list:
With variables, functions, branching, and loops, we have all the tools
we need to create powerful programs. All the rest, as they say, is gravy.
Learning objectives
• You will learn how to use for loops and while loops.
217
218 Loops and iteration
m = [4, 2, 0, 1, 7, 9, 8, 3]
If we ask Python to iterate over this list, the list object itself “knows”
how to return a single member at a time: 4, then 2, then 0, then 1, etc.
while loops 219
We can use this to govern how many iterations we wish to perform and
also to provide data that we can use in our tasks or calculations.
In the following sections, we’ll give a thorough treatment of both kinds
of loop: while and for.
Here the dealer starts with a total of nine. Then, in the while loop, we
keep prompting for the number of points to be added to the dealer’s
hand. Points are added to the value of dealer. This loop will continue to
execute as long as the dealer’s score is less than 17. We see this in the
while condition:
while lst:
print(lst.pop(0))
a
b
c
and then terminate (because after popping the last element, the list is
empty, and thus falsey).
This loop will execute as long as x is less than 100 and x is even.
dealer = 9
When we first reach prompt = "What's the value of the next card drawn? "
this line of code, the
value of dealer is 9, while dealer < 17:
so the condition is true. next_card = int(input(prompt))
We enter the loop and dealer = dealer + next_card
the body of the loop
is executed.
if dealer <= 21:
print(f"Dealer has {dealer} points!")
else:
print(f"Oh, dear. "
f"Dealer has gone over with {dealer} points!")
dealer = 9
dealer = 9
dealer = 9
dealer = 9
dealer = 9
queue = [8, 12, 20, 20, 12, 12, 20, 8, 12, ...]
Let’s call the amount of coffee in the urn reserve, the minimum
minimum, and our queue of customers customers. Our while condition is
reserve >= minimum.
customers_served = 0
they’ve ordered. Once the reserve drops below the minimum, we stop
taking orders and report the results.
serve_coffee(6, lst, 8)
In this case, when we check the while condition the first time, the con-
dition fails, because six is not greater than or equal to eight. Thus, the
body of the loop would never execute, and the function would report:
So it’s possible that the body of any given while loop might never be
executed. If, at the start, the while condition is false, Python will skip
past the loop entirely!
But here’s the problem: We don’t know how many tries it will take
for the user to provide a valid input! Will they do so on the first try? On
the second try? On the fourth try? On the twelfth try? We just don’t
know! Thus, a while loop is the perfect tool.
How would we implement such a loop in Python? What would serve
as a condition?
Input validation with while loops 225
Plot twist: In this case, we’d choose a condition that’s always true,
and then only break out of the loop when we have a number in the desired
range. This is a common idiom in Python (and many other programming
languages).
while True:
n = int(input("Pick a number from 1 to 10: "))
if 1 <= n <= 10:
break
print("Invalid input.")
if n == 7:
print("You have entered 7, "
"which is a very lucky number!")
else:
print(f"You have entered {n}. Good for you!")
Notice what we’ve done here: the while condition is the Boolean lit-
eral True. This can never be false! So we have to have a way of exiting
the loop. That’s where break comes in. break is a Python keyword which
means “break out of the nearest enclosing loop.” The if clause includes
a condition which is only true if the user’s choice is in the desired range.
Therefore, this loop will execute indefinitely, until the user enters a num-
ber between one and 10.
As far as user experience goes, this is much more friendly than just
terminating the program immediately if the user doesn’t follow instruc-
tions. Rather than complaining and exiting, our program can ask again
when it receives invalid input.
A note of caution
While the example above demonstrates a valid use of break, break should
be used sparingly. If there’s a good way to write a while loop without us-
ing break then you should do so! This often involves careful consideration
of while conditions—a worthwhile investment of your time.
It’s also considered bad form to include more than one break statement
within a loop. Again, please use break sparingly.
Comprehension check
1. What is printed?
>>> c = 5
>>> while c >= 0:
... print(c)
... c -= 1
226 Loops and iteration
>>> n = 10
>>> while n > 0:
... n = n // 2
...
Ask yourself:
Challenge!
How about this loop? Try this out with a hand-held calculator.
An ancient algorithm with a while loop 227
EPSILON = 0.01
x = 2.0
guess = x
while True:
guess = sum((guess, x / guess)) / 2
print(guess)
We’ve found the common factors of 120 and 105, which are 3 and 5,
and their product is 15. Therefore, 15 is the greatest common divisor
of 120 and 105. This works, and it may well be what you learned in
elementary algebra, however, it becomes difficult with larger numbers
and isn’t particularly efficient.
Euclid’s algorithm
Euclid was an ancient Greek mathematician who flourished around 300
BCE. Here’s an algorithm that bears Euclid’s name. It was presented
in Euclid’s Elements, but it’s likely that it originated many years before
Euclid.2
2
Some historians believe that Eudoxus of Cnidus was aware of this algorithm (c.
375 BCE), and it’s quite possible it was known before that time.
228 Loops and iteration
"""
Implementation of Euclid's algorithm.
"""
while b != 0:
remainder = a % b
a = b
b = remainder
This new kind of loop is the for loop. for loops are so named because
they iterate for each element in some iterable. Python for loops iterate
over some iterable. Always.4
4
for loops in Python work rather differently than they do in many other lan-
guages. Some languages use counters, and thus for loops are count-controlled. For
example, in Java we might write
See? In our for loop, Python iterated over the elements (a.k.a. “mem-
bers”) of the list provided. It started with 1, then 2, then 3. At that
point the list was exhausted, so the loop terminated.
If it helps, you can read for n in numbers: as “for each number, n, in
the iterable called ‘numbers’.”
This works for tuples as well.
As we iterate over numbers (a list), we get one element from the list at
a time (in the order they appear in the list). So at the first iteration, n
is assigned the value 1. At the second iteration, n is assigned the value 2.
In this case, there’s a counter, i, which is updated at each iteration of the loop.
Here we update by incrementing i using ++i (which in Java increments i). The loop
runs so long as the control condition i < 10 is true. On the last iteration, with i equal
to nine, i is incremented to ten, then the condition no longer holds, and the loop
exits. This is not how for loops work in Python! Python for loops always iterate
over an iterable.
for loops 231
At the third iteration, n is assigned the value 3. After the third iteration,
there are no more elements left in the sequence and the loop terminates.
Thus, the syntax of a for loop requires us to give a variable name for
the variable which will hold the individual elements of the sequence. For
example, we cannot do this:
If we were to try this, we’d get a SyntaxError. The syntax that must
be used is:
where <some variable> is replaced with a valid variable name, and <some
iterable> is the name of some iterable, be it a list, tuple, string, or other
iterable.
>>> r = range(4)
>>> len(r)
4
5
An arithmetic sequence, is a sequence of numbers such that the difference be-
tween any number in the sequence and its predecessor is constant. 1, 2, 3, 4, …is an
arithmetic sequence because the difference between each of the terms is 1. Similarly,
2, 4, 6, 8, …is an arithmetic sequence because the difference between each term is 2.
Python range objects are restricted to arithmetic sequences of integers.
232 Loops and iteration
>>> r[0]
0
>>> r[1]
1
>>> r[2]
2
>>> r[3]
3
>>> r[4]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: range object index out of range
We see that the values held by this range object, are 0, 1, 2, and 3, in
that order.
Now let’s use a range object in a for loop. Here’s the simplest possible
example:
Zero through three. range(n) with a single integer argument will generate
an arithmetic sequence from 0 up to, but not including, the value of the
argument.
Notice, though, that if we use range(n) our loop will execute n times.
What if we wanted to iterate integers in the interval [5, 10]? How
would we do that?
7
8
9
10
This yields a range which goes from 18, down to but not including 2,
counting backward by threes.
So you see, range() is pretty flexible.
Comprehension check
1. What is the evaluation of sum(range(5))?
11.6 Iterables
As we have seen, iterables are Python’s way of controlling a for loop.
You can think of an iterable as a sequence or composite object (com-
posed of many parts) which can return one element at a time, until the
sequence is exhausted. We usually refer to the elements of an iterable as
members of the iterable.
It’s much like dealing playing cards from a deck, and doing something
(performing a task or calculation) once for each card that’s dealt.
A deck of playing cards is an iterable. It has 52 members (the indi-
vidual cards). The cards have some order (they may be shuffled or not,
but the cards in a deck are ordered nonetheless). We can deal cards one
at a time. This is iterating through the deck. Once we’ve dealt the 52nd
card, the deck is exhausted, and iteration stops.
Now, there are two ways we could use the cards.
First, we can use the information that’s encoded in each card. For
example, we could say the name of the card, or we could add up the pips
on each card, and so on.
Alternatively, if we wanted to do something 52 times (like push-ups)
we could do one push-up for every card that was dealt. In this case, the
information encoded in each card and the order of the individual cards
would be irrelevant. Nevertheless, if we did one push-up for every card
that was dealt, we’d know when we reached the end of the deck that
we’d done 52 push-ups.
So it is in Python. When iterating some iterable, we can use the data
or value of each member (say calculating the sum of numbers in a list),
or we can just use iteration as a way of keeping count. Both are OK in
Python.
Iterables 235
evens = 0
for n in lst:
if n % 2 == 0: # it's even
evens += 1
"""
Produce a list of Halley's type periodic comets
with orbital period less than 100 years.
"""
COMETS = [('Mellish', 145), ('Sheppard–Trujillo', 66),
('Levy', 51), ('Halley', 75), ('Borisov', 152),
('Tsuchinshan', 101), ('Holvorcem', 38)]
# This list is abridged. You get the idea.
short_period_comets = []
Here we’re using the data encoded in each member of the iterable,
COMETS.
6
The orbital period of a comet is the time it takes for the comet to make one
orbit around the sun.
236 Loops and iteration
for _ in range(1_000_000):
print("I will not waste chalk")
Here we’re not using the data encoded in the members of the iterable.
Instead, we’re just using it to keep count. Accordingly, we’ve given the
variable which holds the individual members returned by the iterable the
name _. _ is commonly used as a name for a variable that we aren’t going
to use in any calculation. It’s the programmer’s way of saying, “Yeah,
whatever, doesn’t matter what value it has and I don’t care.”
So these are two different ways to treat an iterable. In one case, we
care about the value of each member of the iterable; in the other, we
don’t. However, both approaches are used to govern a for loop.
sum_ = 0
for n in t:
sum_ += n
If you’ve not seen the symbol ∑ before, it’s just shorthand for “add
them all up.”
What’s the connection between summations and loops? The summa-
tion is a loop!
In the formula above, there’s some list of values, 𝑥 indexed by 𝑖. The
summation says: “Take all the elements, 𝑥𝑖 , and sum them.” The sum-
mation portion is just
𝑁−1
∑ 𝑥𝑖
𝑖=0
s = 0
for e in x:
s = s + e
mean = s / len(x)
Yes, we could calculate the sum with sum() but what do you think sum()
does behind the scenes? Exactly this!
7
Why do we use the trailing underscore? To avoid overwriting the Python built-
in function sum() with a new definition.
238 Loops and iteration
s = 0
for e in x:
s = s + e ** 2
11.10 Products
The same applies to products. Just as we can sum by adding all the
elements in some list or tuple of numerics, we can also take their product
by multiplying. For this, instead of the symbol ∑, we use the symbol Π
(that’s an upper-case Π to distinguish it from the constant 𝜋).
𝑁−1
∏ 𝑥𝑖
𝑖=0
p = 1
for e in x:
p = p * e
11.11 enumerate()
We’ve seen how to iterate over the elements of an iterable in a for loop.
for e in lst:
print(e)
Sometimes, however, it’s helpful to have both the element and the index
of the element at each iteration. One common application requiring an
element and the index of the element is in calculating an alternating
enumerate() 239
alternating_sum = 0
for i in range(len(lst)):
if i % 2: # i is odd, then we subtract
alternating_sum -= lst[i]
else:
alternating_sum += lst[i]
alternating_sum = 0
for i, e in enumerate(lst):
if i % 2:
alternating_sum -= e
else:
alternating_sum += e
This prints:
So you see, what’s yielded at each iteration is a tuple of index and ele-
ment. Pretty cool, huh?
Now that we’ve learned a little about enumerate() let’s revisit the
alternating sum example:
alternating_sum = 0
for i, e in enumerate(lst):
if i % 2:
alternating_sum -= e
else:
alternating_sum += e
s = 0
for n in range(1, 10):
if n % 2:
# n is odd; 1 is truthy
s = s + 1 / n
else:
# n must be even; 0 is falsey
s = s - 1 / n
Let’s make a table, and fill it out. The first row in the table will
represent our starting point, subsequent rows will capture what goes on
in the loop. In this table, we need to keep track of two things, n and s.
n s
n s
0
1 ?
2 ?
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?
n s
0
1 1
2 ?
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?
Now for the next iteration. At the next iteration, n takes on the value
2. Which branch executes? Well, 2 is even, so the else branch will execute
and 1/2 will be subtracted from s. Let’s not perform decimal expansion,
so we can write:
n s
0
1 1
2 1 − 1/2
3 ?
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?
Now for the next iteration. n takes on the value 3, 3 is odd, and so
the if branch executes and we add 1/3 to s. Again, let’s not perform
decimal expansion (not doing so will help us see the pattern that will
emerge).
n s
0
1 1
2 1 − 1/2
3 1 − 1/2 + 1/3
4 ?
5 ?
6 ?
7 ?
8 ?
9 ?
Now for the next iteration. n takes on the value 4, 4 is even, and so
the else branch executes and we subtract 1/4 to s.
Tracing a loop 243
n s
0
1 1
2 1 − 1/2
3 1 − 1/2 + 1/3
4 1 − 1/2 + 1/3 − 1/4
5 ?
6 ?
7 ?
8 ?
9 ?
Do you see where this is heading yet? No? Let’s do a couple more
iterations.
At the next iteration. n takes on the value 5, 5 is odd, and so the if
branch executes and we add 1/5 to s. Then n takes on the value 6, 6 is
even, and so the else branch executes and we subtract 1/6 to s.
n s
0
1 1
2 1 − 1/2
3 1 − 1/2 + 1/3
4 1 − 1/2 + 1/3 − 1/4
5 1 − 1/2 + 1/3 − 1/4 + 1/5
6 1 − 1/2 + 1/3 − 1/4 + 1/5 − 1/6
7 ?
8 ?
9 ?
At this point, it’s likely you see the pattern (if not, just work out two
or three more iterations). This loop is calculating
1 1 1 1 1 1 1 1
1− + − + − + − +
2 3 4 5 6 7 8 9
See? At each iteration, we’re checking to see if n is even or odd. If n is
odd, we add 1 / n; if n is even, we subtract 1 / n.
We can write this more succinctly using summation notation. This
loop calculates
𝑛=9
1
𝑠 = ∑(−1)𝑛−1 .
𝑛=1
𝑛
You may ask yourself: What’s up with the (−1)𝑛−1 term? That’s han-
dling the alternating sign!
• What’s (−1)0 ? 1.
• What’s (−1)1 ? -1.
• What’s (−1)2 ? 1.
244 Loops and iteration
n = 6
f = 1
for i in range(2, n + 1):
f = f * i
i f
1
2 2
3 6
4 24
5 120
6 720
This calculates factorial, 𝑛! for some 𝑛. (Yes, there are easier ways.)
Remember:
𝑖=𝑛
𝑛 ! = ∏ 𝑖.
𝑖=1
birth_rate = 0.05
death_rate = 0.03
pop = 1000 # population
n = 4
for _ in range(n):
pop = int(pop * (1 + birth_rate - death_rate))
Nested loops 245
_ p
1000
1 1020
2 1040
3 1060
4 1081
Here we don’t use the value of the loop index in our calculations, but
we include it in our table just to keep track of which iteration we’re on. In
this example, we multiply the old pop by (1 + birth_rate - death_rate)
and make this the new pop at each iteration. This one’s a nuisance to
work out by hand, but with a calculator it’s straightforward.
What is this calculating? This is calculating the size of a population
which starts with 1000 individuals, and which has a birth rate of 5%
and a death rate of 3%. This calculates the population after four time
intervals (e.g., years).
Being able to trace through a loop (or any portion of a program) is a
useful skill for a programmer.
This code, when executed, prints exactly the list of pairings shown above.
How does this work? The outer loop—for white in players:—iterates
over all players, one at a time: Anne, Bojan, Carlos, and Doris. For each
iteration of the outer loop, there’s an iteration of the inner loop, again:
Anne, Bojan, Carlos, and Doris. If the element assigned to white in the
outer loop does not equal the element assigned to black in the inner loop,
we print the pairing. In this way, all possible pairings are generated.
Here’s another example—performing multiplication using a nested
loop. (What follows is inefficient, and perhaps a little silly, but hopefully
it illustrates the point.)
Let’s say we wanted to multiply 5 × 7 without using the * operator.
We could do this with a nested loop!
answer = 0
for _ in range(5):
for __ in range(7):
answer += 1
print(answer) # prints 35
How many times does the outer loop execute? Five. How many times
does the inner loop execute for each iteration of the outer loop? Seven.
How many times do we increment answer? 5 × 7 = 35.
game = [
['X', ' ', 'O'],
['O', 'X', 'O'],
['X', ' ', 'X']
]
This prints
X O
OXO
X X
Stacks
A stack is what’s called a last in, first out data structure (abbreviated
LIFO and pronounced life-o). It’s a linear data structure where we add
elements to a list at one end, and remove them from the same end.
The canonical example for a stack is cafeteria trays. Oftentimes these
are placed on a spring-loaded bed, and cafeteria customers take the tray
off the top and the next tray in the stack is exposed. The first tray to be
put on the stack is the last one to be removed. You’ve likely seen chairs
that can be stacked. The last one on is the first one off. Have you ever
packed a suitcase? What’s gone in last is the first to come out. Have you
ever hit the ‘back’ button in your web browser? Web browsers use a stack
to keep track of the pages you’ve visited. Have you ever used ctrl-z to
undo an edit to a document? Do you have a stack of dishes or bowls in
your cupboard? Guess what? These are all everyday examples of stacks.
We refer to appending an element to a stack as pushing. We refer to
removing an element to a stack as popping (this should sound familiar).
248 Loops and iteration
Stacks are very widely used in computer science and stacks are at the
heart of many important algorithms. (In fact, function calls are managed
using a stack!)
The default behavior for a list in Python is to function as a stack.
Yes, that’s right, we get stacks for free! If we append an item to a list,
it’s appended at one end. When we pop an item off a list, by default, it
pops from the same end. So the last element in a Python list represents
the top of the stack.
Here’s a quick example:
>>> stack = []
>>> stack.append("Pitchfork")
>>> stack.append("Spotify")
>>> stack.append("Netflix")
>>> stack.append("Reddit")
>>> stack.append("YouTube")
>>> stack[-1] # see what's on top
YouTube
>>> stack.pop()
YouTube
Stacks and queues 249
Queues
A queue is a first in, first out linear data structure (FIFO, pronounced
fife-o). The only difference between a stack and a queue is that with a
stack we push and pop items from the same end, and with a queue we
add elements at one end and remove them from the other. That’s the
only difference.
What are some real world examples? The checkout line at a grocery
store—the first one in line is the first to be checked out. Did you ever
wait at a printer because someone had sent a job before you did? That’s
another queue. Cars through a toll booth, wait lists for customer service
chats, and so on—real world examples abound.
The terminology is a little different. We refer to appending an element
to a queue as enqueueing. We refer to removing an element to a queue as
dequeueing. But these are just fancy names for appending and popping.
Like stacks, queues are very widely used in computer science and are
at the heart of many important algorithms.
There’s one little twist needed to turn a list into a queue. With a
queue, we enqueue from one end and dequeue from the other. Like a
stack, we can use append to enqueue. The little twist is that instead of
.pop() which would pop from the same end, we use .pop(0) to pop from
the other end of the list, and voilà, we have a queue.
Here’s a quick example:
250 Loops and iteration
queue = []
>>> queue.append("Fred") # Fred is first in line
>>> queue.append("Mary") # Mary is next in line
>>> queue.append("Claire") # Claire ie behind Mary
>>> queue.pop(0) # Fred has been served
'Fred'
>>> queue[0] # now see who's in front
'Mary'
>>> queue.append("Gladys") # Gladys gets in line
>>> queue.pop(0) # Mary's been served
'Mary'
When we ask Python to iterate over some iterable, it calls the function
iter()which returns an iterator for the iterable (in this case a list).
The way iterators work is that Python keeps asking “give me the next
member”, until the iterator is exhausted. This is done (behind the scenes)
by calls to the iterator’s __next__() method.
>>> iterator.__next__()
'Greninja'
>>> iterator.__next__()
'Lucario'
>>> iterator.__next__()
'Mimikyu'
>>> iterator.__next__()
'Charizard'
>>> iterator.__next__()
Traceback (most recent call last):
File "/Library/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
StopIteration
>>> r = range(5)
>>> type(r)
<class 'range'>
But ranges are iterable, so they work with the same function iter(r),
and the resulting iterator will have __next__() as a method.
11.16 Exercises
Exercise 01
Write a while loop that adds all numbers from one to ten. Do not use
for. What is the sum? Check your work. (See: section 11.2)
Exercise 02
Without a loop but using range(), calculate the sum of all numbers from
one to ten. Check your work. (See: section 11.5)
Exercise 03
a. Write a for loop that prints the numbers 0, 2, 4, 6, 8, each on a
separate line.
b. Write a for loop that prints some sentence of your choosing five
times.
Exercise 04
Write a for loop that calculates the sum of the squares of some arbitrary
list of numerics, named data (make up your own list, but be sure that it
has at least four elements).
For example, if the list of numerics were [2, 9, 5, -1], the result
would be 111, because
2×2=4
9 × 9 = 81
5 × 5 = 25
−1 × −1 = 1
and 4 + 81 + 25 + 1 = 111.
(See: section 11.6)
Exercise 05
Write a for loop that iterates the list
lst = [23, 7, 42, 17, 9, 38, 28, 31, 49, 22, 5, 26, 15]
and prints the parity sum of the list. That is, if the number is even, add
it to the total; if the number is odd, subtract it from the total.
What is the sum? Check your work. Double-check your work.
(See: section 11.11)
Exercise 06
Write a for loop which iterates over a string and capitalizes every other
letter. For example, with the string “Rumplestiltskin”, the result should
be “RuMpLeStIlTsKiN”. With the string “HELLO WORLD!”, the result
Exercises 253
Exercise 07
Create some list of your own choosing. Your list should contain at least
five elements. Once you’ve created your list, write a loop that uses
enumerate() to iterate over your list, yielding both index and element
at each iteration. Print the results indicating the element and its index.
For example, given the list
Exercise 08 (challenge!)
The Fibonacci sequence is a sequence of integers, starting with 0 and 1,
such that after these first two, each successive number in the sequence is
the sum of the previous two numbers. So, for example, the next number
is 1 because 0 + 1 = 1, the number after that is 2 because 1 + 1 = 2,
the number after that is 3 because 1 + 2 = 3, the number after that is 5
because 2 + 3 = 5, and so on.
Write a program that uses a loop (not recursion) to calculate the first
𝑛 terms of the Fibonacci sequence. Start with this list:
fibos = [0, 1]
You may use one call to input(), one if statement, and one while loop.
You may not use any other loops. You may not use recursion. Examples:
Exercise 09
Write a function that takes a list as an argument and modifies the list in
a loop. How you modify the list is up to you, but you should use at least
two different list methods. Include code that calls the function, passing
in a list variable, and then demonstrates that the list has changed, once
the function has returned.
Exercise 10
Write a function which takes two integers, 𝑛 and 𝑘 as arguments, and
produces a list of all odd multiples of 𝑘 between 1 and 𝑛. E.g., given
input 𝑛 = 100 and 𝑘 = 7, the function should return
Exercise 11 (challenge!)
The modulo operator partitions the integers into equivalence classes
based on their residues (remainders) with respect to some modulus. For
example, with a modulus of three, the integers are partitioned into three
equivalence classes: those for which 𝑛 mod 3 ≡ 0, 𝑛 mod 3 ≡ 1, and 𝑛
mod 3 ≡ 2.
Write and test a function which takes as arguments an arbitrary list
of integers and some modulus, 𝑛, and returns a tuple containing the
count of elements in the list in each equivalence class, where the index
of the tuple elements corresponds to the residue. So, for example, if the
input list were [1, 5, 8, 2, 11, 15, 9] and the modulus were 3, then
the function should return the tuple (2, 1, 4), because there are two
elements with residue 0 (15 and 9), one element with residue 1, (1), and
four elements with residue 2 (5, 8, 2, 11).
Notice also that if the modulus is 𝑛, the value returned will be an
𝑛-tuple.
Exercises 255
Exercise 12
Use a Python list as a stack. Start with an empty list:
stack = []
1. push ‘teal’
2. push ‘magenta’
3. push ‘yellow’
4. push ‘viridian’
5. pop
6. pop
7. push ‘amber’
8. pop
9. pop
10. push ‘vermilion’
['teal', 'vermilion']
Exercise 13
At the Python shell, create a list and use it as a queue. At the start, the
queue should be empty.
>>> queue = []
1. enqueue ‘red’
2. enqueue ‘blue’
3. dequeue
4. enqueue ‘green’
5. dequeue
6. enqueue ‘ochre’
7. enqueue ‘cyan’
8. enqueue ‘umber’
9. dequeue
10. enqueue ‘mauve’
Now, print your queue. Your queue should look like this:
Here we will learn about some of the abundant uses of randomness. Ran-
domness is useful in games (shuffle a deck, roll a die), but it’s also useful
for modeling and simulating a staggering variety of real world processes.
Learning objectives
• You will understand why it is useful to be able to generate pseudo-
random numbers or make pseudo-random choices in a computer
program.
• You will learn how to use some of the most commonly-used methods
from Python’s random module, including
– random.random() to generate a pseudo-random a floating point
number in the interval [0.0, 1.0),
– random.randint() to generate a pseudo-random integer in a
specified interval,
– random.choice() to make a pseudo-random selection of an item
from an iterable object, and
– random.shuffle() to shuffle a list.
• You will understand the role of a seed in the generation of
pseudo-random numbers, and understand how setting a seed
makes predictable the behavior of a program incorporating pseudo-
randomness.
Terms introduced
• Monte Carlo simulation
• pseudo-random
• random module
• random walk
• seed
257
258 Randomness, games, and simulations
import random
random.choice()
The random.choice() method takes an iterable and returns a pseudo-
random choice from among the elements of the iterable. This is useful
when selecting from a fixed set of possibilities. For example:
Each time we call choice this way, it will make a pseudo-random choice
between ‘heads’ and ‘tails’, thus simulating a coin toss.
The random module 259
>>> random.choice("random")
'm'
Comprehension check
1. How could we use random.choice() to simulate the throw of a six-
sided die?
2. How could we use random.choice() to simulate the throw of a twelve-
sided die?
>>> position = 0
>>> for _ in range(5):
... position = position + random.choice([-1, 1])
...
random.random()
This method returns the next pseudo-random floating point number in
the interval [0.0, 1.0). Note that the interval given here is in mathematical
notation and is not Python syntax. Example:
x = random.random()
What use is this? We can use this to simulate events with a certain
probability, 𝑝. Recall that probabilities are in the interval [0.0, 1.0], where
0.0 represents impossibility, and 1.0 represents certainty. Anything be-
tween these two extremes is interesting.
Comprehension check
1. How would we generate a pseudo-random number in the interval
[0.0, 10.0)?
Comprehension check
1. How would we simulate an event which occurs with a probability
of 1/4?
2. How would we generate a pseudo-random floating point number in
the interval [−2.0, 2.0)?
random.randint()
As noted, we can use random.choice() to choose objects from some iter-
able. If we wanted to pick a number from one to ten, we could use
n = random.choice([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
n = random.randint(1, 1000)
random.shuffle()
Sometimes, we want to shuffle values, for example a deck of cards.
random.shuffle() will shuffle a mutable sequence (e.g., a list) in place.
Example:
cards = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10',
'J', 'Q', 'K']
random.shuffle(cards)
Comprehension check
1. random.shuffle() works with a list. Why wouldn’t it work with a
tuple? Would it work with a string?
2. Where’s the bug in this code?
Try this out. (The sequence of numbers you’ll get will differ.)
So where does the seed come in? By default, the algorithm gets a
seed from your computer’s operating system. Modern operating systems
provide a special source for this, and if a seed is not supplied in your
code, the random module will ask the operating system to supply one.2
This is where the random module gets its seed by default. This service itself requires
a seed, which the OS gets from a variety of hardware sources. The objective is for
the seed to be as unpredictable as possible.
Using the seed 263
12.4 Exercises
Exercise 01
Use random.choice() to simulate a fair coin toss. This method takes an
iterable, and at each call, chooses one element of the iterable at random.
For example,
random.choice([1, 2, 3, 4, 5)]
will choose one of the elements of the list, each with equal probability.
In a loop simulate 10 coin tosses. Then report the number of heads
and the number of tails.
Exercise 02
Use random.random() to simulate a fair coin toss. Remember that
random.random() returns afloating point number in the interval [0.0, 1.0).
In a loop simulate 10 coin tosses. Then report the number of heads
and the number of tails.
Exercise 03
Simulate a biased coin toss. You may assume that, in the limit, the
biased coin comes up heads 51.7% of the time. Unlike Exercise 01,
random.choice() won’t work because outcomes are not equally probable.
In a loop simulate 10 such biased coin tosses. Then report the number
of heads and the number of tails.
Exercise 04
random.shuffle() takes some list as an argument and shuffles the list in-
place. (Remember, lists are mutable, and shuffling in place means that
random.shuffle() will modify the list you pass in and will return None.)
Write a program that shuffles the list
and then using .pop() in a while loop “deal the cards” one at a time
until the list is exhausted. Print each card as it is popped from the list.
Exercise 05
The gambler’s ruin simulates a gambler starting with some amount of
money and gambling until they run out. Probability theory tells us they
will always run out of money—it’s just a matter of time.
Write a program which prompts the user for some amount of money
and then simulates the gambler’s ruin by betting on a fair coin toss. Use
an integer value for the money, and wager one unit on each coin toss.
Your program should report the number of coin tosses it took the
gambler to go bust.
Exercises 265
Exercise 06
Write a program that simulates the throwing of two six-sided dice.
In a loop, simulate the throw, and report the results. For example, if
the roll is a two and a five, print “2 + 5 = 7”. Prompt the user, asking
if they want to roll again or quit.
Exercise 07 (challenge!)
Write a program that prompts the user for a number of throws, 𝑛, and
then simulates 𝑛 throws of two six-sided dice. Record the total of dots
for each throw. To record the number of dots use a list of integers. Start
with a list of all zeros.
counts = [0] * 13
# This gets you a list of all zeros like this:
# [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# We're going to ignore the element at index zero
Then, for each throw of the dice, calculate the total number of dots
and increment the corresponding element in the list. For example, if the
first three throws are five, five, and nine, then counts should look like
this
[0, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0]
After completing 𝑛 rolls, print the result (again, ignoring the element
at index 0) and verify with an assertion that the sum of the list equals
𝑛.
Exercise 08
In mathematics, one of the requirements of a function is that given the
same input it produces the same output. Always.
For example, the square of 2 is always 4. You can’t check back later
and find that it’s 4.1, or 9, or something else. Applying a function to the
same argument will always yield the same result. If this is not the case,
then it’s not a function.
Question: Are the functions in the random module truly functions? If
so, why? If not, why not?
Exercise 09 (challenge!)
Revisit the gambler’s ruin from Exercise 05.
Modify the program so that it runs 1,000 simulations of the gambler’s
ruin, and keeps track of how many times it took for the gambler to run
out of money. However—and this is important—always start with the
same amount of money (say 1,000 units of whatever currency you like).
Then calculate the mean and standard deviation of the set of simula-
tions.
The mean, written 𝜇, is given by
266 Randomness, games, and simulations
1 𝑁−1
𝜇= ∑𝑥
𝑁 𝑖=0 𝑖
where we have a set of outcomes, 𝑋, indexed by 𝑖, with 𝑁 equal to the
number of elements in 𝑋.
The standard deviation, written 𝜎, is given by
1 𝑁−1
𝜎= ∑ (𝑥 − 𝜇)2 .
𝑁 𝑖=0 𝑖
Once that’s done, run the program again, separately, for 10,000,
100,000, and 1,000,000 simulations, and record the results.
Does the mean change with the number of simulations? How about
the standard deviation?
What does all this tell you about gambling?
Hint: It makes sense to write separate functions for calculating mean
and standard deviation.
Chapter 13
File I/O
So far all of the input to our programs and all of the output from our
programs has taken place in the console. That is, we’ve used input() to
prompt for user input, and we’ve used print() to send output to the
console.
Of course, there are a great many ways to send information to a pro-
gram and to receive output from a program: mice and trackpads, audio
data, graphical user interfaces (“GUIs”, pronounced “gooeys”), temper-
ature sensors, accelerometers, databases, actuators, networks, web APIs
(application program interfaces), and more.
Here we will learn how to read data from a file and write data to a
file. We call this “file i/o” which is short for “file input and output.” This
is particularly useful when we have large amounts of data to process.
In order to read from or write to a file, we need to be able to open
and close a file. We will do this using a context manager.
We will also see new exceptions which may occur when attempting to
read from or write to a file, specifically FileNotFoundError.
Learning objectives
• You will learn how to read from a file.
• You will learn some of the ways to write to a file.
• You will learn some valuable keyword arguments.
• You will learn about the csv file format and Python module, as
well as how to read from and write to a .csv file.
267
268 File I/O
When we exit this block (i.e. all the indented code has executed), Python
will close the file automatically. Without this context manager, we’d need
to call .close() explicitly, and failure to do so can lead to unexpected
and undesirable results.
with and as are Python keywords. Here, with creates the context man-
ager, and as is used to give a name to our file object. So once the file is
opened, we may refer to it by the name given with as—in this instance
fh (a common abbreviation for “file handle”).
>>> lines = []
>>> with open('poem.txt') as f:
... for line in f:
... lines.append(line)
...
Writing to a file 269
>>> print(lines)
["Flood-tide below me!\n", "I see you face to face\n",
"Clouds of the west--\n", "Sun there half an hour high--\n",
"I see you also face to face.\n"]
Now, when we look at the data this way, we see clearly that newline
characters are included at the end of each line. Sometimes we wish to
remove this. For this we use the string method .strip().
>>> lines = []
>>> with open('poem.txt') as f:
... for line in f:
... lines.append(line.strip())
...
>>> print(lines)
["Flood-tide below me!", "I see you face to face",
"Clouds of the west--", "Sun there half an hour high--",
"I see you also face to face."]
>>>
The string method .strip() without any argument removes any lead-
ing or trailing whitespace, newlines, or return characters from a string.
If you only wish to remove newlines ('\n'), just use s.strip('\n')
where s is some string.
Character Meaning
'r' open for reading (default)
'w' open for writing, truncating the file first
'x' open for exclusive creation, failing if the file already exists
'a' open for writing, appending to the end of the file if it
exists
'b' binary mode
't' text mode (default)
'+' open for updating (reading and writing)
f = open('hello.txt', 'w')
f.write('Hello World')
f.close()
but then it’s our responsibility to close the file when done. The idiom
with open('hello.txt', 'w') as f: will take care of closing the file
automatically, as soon as the block is exited.
Now let’s write a little more data. Here’s a snippet from a poem by
Walt Whitman (taking some liberties with line breaks):
1
https://docs.python.org/3/library/functions.html#open
2
It is often the case that we wish to write binary data to file, but doing so is
outside the scope of this text.
Keyword arguments 271
Here we simply iterate through the lines in this fragment and write them
to the file poem.txt. Notice that we include newline characters '\n' to
end each line.
import random
Since we can forget to call f.close(), use of with is the preferred (and
most Pythonic) approach.
Comprehension check
1. Try the above code snippets to write to files hello.txt and
poem.txt.
based on their position. The base is the first argument. The exponent is
the second argument.
Some functions allow for keyword arguments. Keyword arguments fol-
low positional arguments, and are given a name when calling the func-
tion. For example, print() allows you to provide a keyword argument
end which can be used to override the default behavior of print() which
is to append a newline character, \n, with every call. Example:
print("Cheese")
print("Shop")
prints “Cheese” and “Shop” on two different lines, because the default
is to append that newline character. However…
open('my_data.csv', newline='')
This method allows more complex behavior (but I find the use cases
rare). For more on .strip() see: https://docs.python.org/3/library/stdt
ypes.html?highlight=strip#str.strip
Year,FIFA champion
2018,France
2014,Germany
2010,Spain
2006,Italy
2002,Brazil
Pretty simple.
What happens if we have commas in our data? Usually numbers don’t
include comma separators when in CSV format. Instead, commas are
274 File I/O
added only when data are displayed. So, for example, we might have
data like this (using format specifiers):
Country,2018 population
China,1427647786
India,1352642280
USA,327096265
Indonesia,267670543
Pakistan,212228286
Brazil,209469323
Nigeria,195874683
Bangladesh,161376708
Russia,145734038
Building Address
Waterman 85 Prospect St, Burlington, VT
Innovation 82 University Pl, Burlington, VT
Building,Street,City,State
Waterman,85 Prospect St,Burlington,VT
Innovation,82 University Pl,Burlington,VT
What if we really, really had to have commas in our data? Oh, OK.
Here are cousin David’s favorite bands of all time:
The csv module 275
Band Rank
Lovin’ Spoonful 1
Sly and the Family Stone 2
Crosby, Stills & Nash 3
Earth, Wind and Fire 4
Herman’s Hermits 5
Iron Butterfly 6
Blood, Sweat & Tears 7
The Monkees 8
Peter, Paul & Mary 9
Ohio Players 10
Now there’s no way around commas in the data. For this we wrap the
data including commas in quotation marks.
Band,Rank
Lovin' Spoonful,1
Sly and the Family Stone,2
"Crosby, Stills, Nash and Young",3
"Earth, Wind and Fire",4
Herman's Hermits,5
Iron Butterfly,6
"Blood, Sweat & Tears",7
The Monkees,8
"Peter, Paul & Mary",9
Ohio Players,10
(We’ll save the case of data which includes commas and quotation marks
for another day.)
We can read data like this using Python’s csv module.
import csv
with open('bands.csv', newline='') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(row)
This prints:
['Band', 'Rank']
["Lovin' Spoonful", '1']
['Sly and the Family Stone', '2']
['Crosby, Stills, Nash and Young', '3']
['Earth, Wind and Fire', '4']
["Herman's Hermits", '5']
['Iron Butterfly', '6']
['Blood, Sweat & Tears', '7']
['The Monkees', '8']
['Peter, Paul & Mary', '9']
['Ohio Players', '10']
276 File I/O
import csv
This writes
Deerhoof,1
Lightning Bolt,2
Radiohead,3
Big Thief,4
King Crimson,5
French for Rabbits,6
Yak,7
Boygenius,8
Tipsy,9
My Bloody Valentine,10
to the file.
13.7 Exceptions
FileNotFoundError
This is just as advertised: an exception which is raised when a file is not
found. This is almost always due to a typo or misspelling in the filename,
or that the correct path is not included.
Suppose there is no file in our file system with the name
some_non-existent_file.foobar. Then, if we were to try to open a file
without creating it, we’d get a FileNotFoundError.
13.8 Exercises
Exercise 01
Write a program which writes the following lines (including blank lines)
to a file called bashos_frog.txt.
Basho's Frog
Exercise 02
Download the text file at https://www.uvm.edu/~cbcafier/cs1210/b
ook/data/random_floats.txt, and write a program which reads it and
reports how many lines it contains.
278 File I/O
Exercise 03
There’s a bug in this code.
import csv
prices = []
Exercise 04
Write a program which writes 10 numbers to a file, closes the file, then
reads the 10 numbers from the file, and verifies the correct result. Use
assertions to verify.
Exercises 279
Exercise 05
Here’s a poem, which is saved with the filename doggerel.txt.
When run, this results in a blank line being printed between every line
in the poem.
I cannot rhyme.
What follows in this chapter is merely a tiny peek into the huge topic of
data analysis and presentation. This is not intended to be a substitute for
a course in statistics. There are plenty of good textbooks on the subject
(and plenty of courses at any university), so what’s presented here is just
a little something to get you started in Python.
Learning objectives
• You will gain a rudimentary understanding of two important de-
scriptive statistics: the mean and standard deviation.
• You will understand how to calculate these statistics and be able
to implement them on your own in Python.
• You will learn the basics of Matplotlib’s Pyplot interface, and be
able to use matplotlib.pyplot to create line, bar, and scatter plots.
Terms introduced
• arithmetic mean
• central tendency
• descriptive statistics
• Matplotlib
• normal distribution
• quantile (including quartile, quintile, percentile)
• standard deviation
281
282 Data analysis and presentation
different ways to calculate the mean of a data set. Here we will present
the arithmetic mean (average). The standard deviation is a measure of
the amount of variation observed in a data set.
We’ll also look briefly at quantiles, which provide a different perspec-
tive from which to view the spread or variation in a data set.
Country Value
Bangladesh 0.69
Belgium 11.33
Bhutan 0.97
Brazil 6.68
Brunei 2.38
Cameroon 0.049
Chad 0.011
Chile 14.81
Colombia 8.26
Costa Rica 10.58
Cyprus 8.58
… …
1
Source: World Health Organization: https://www.who.int/data/gho/data/
indicators/indicator- details/GHO/dentists- (per- 10- 000- population) (retrieved
2023-07-07)
Some elementary statistics 283
data = []
with open('dentists_per_10k.csv', newline='') as fh:
reader = csv.reader(fh)
next(reader) # skip the first row (column headings)
for row in reader:
data.append(float(row[1]))
def mean(lst):
return sum(lst) / len(lst)
We take all the 𝑥𝑖 and add them up (with sum()). Then we get the number
of elements in the set (with len()) and use this as the divisor. If we print
the result with print(f"{mean(data):.4f}") we get 5.1391.
That tells us a little about the data set: on average (for the sample
of countries included) there are a little over five dentists per 10,000 pop-
ulation. If everyone were to go to the dentist once a year, that would
suggest, on average, that each dentist serves a little less than 2,000 pa-
tients per year. With roughly 2,000 working hours in a year, that seems
plausible. But the mean doesn’t tell us much more than that.
To get a better understanding of the data, it’s helpful to understand
how values are distributed about the mean.
Let’s say we didn’t know anything about the distribution of values
about the mean. It would be reasonable for us to assume these values
are normally distributed. There’s a function which describes the normal
distribution, and you’ve likely seen the so-called “bell curve” before.
284 Data analysis and presentation
On the 𝑥-axis are the values we might measure, and on the 𝑦-axis we
have the probability of observing a particular value. In a normal distri-
bution, the mean is the most likely value for an observation, and the
greater the distance from the mean, the less likely a given value. The
standard deviation tells us how spread out values are about the mean. If
the standard deviation is large, we have a broad curve:
When we have real-world data, it’s not often perfectly normally dis-
tributed. By comparing our data with what would be expected if it were
normally distributed we can learn a great deal.
Returning to our dentists example, we can look for possible outliers
by iterating through our data and finding any values that are greater
than two standard deviations from the mean.
m = mean(data)
std = std_dev(data)
outliers = []
for datum in data:
if abs(datum) > m + 2 * std:
outliers.append(datum)
import csv
import statistics
data = []
with open('dentists_per_10k.csv', newline='') as fh:
reader = csv.reader(fh)
next(reader) # skip the first row
for row in reader:
data.append(float(row[1]))
print(f"{statistics.mean(data):.4f}")
print(statistics.mean(data))
print(f"{statistics.pstev(data):.4f}")
When we run this, we see that the results for mean and standard
deviation—5.1391 and 4.6569, respectively—are in perfect agreement
with the results reported above.
The statistics module comes with a great many functions including:
• mean()
• median()
• pstdev()
• stdev()
• quantiles()
among others.
test. If your score was in the 95th percentile, then you’re in the top 5%
all those who took the test.
Let’s use the statistics module to find quintiles for our dentists data
(recall that quintiles divide the distribution into five parts).
If we import csv and statistics and then read our data (as above),
we can calculate the values which divide the data into quintiles thus:
Notice that we pass the data to the function just as we did with mean()
and pstdev(). Here we also supply a keyword argument, n=5, to indicate
we want quintiles. When we print the result, we get
Notice we have four values, which divide the data into five equal parts.
Any value below 0.274 is in the first quintile. Values between 0.274 and
0.236 (rounding) are in the second quartile, and so on. Values above
8.826 are in the fifth quintile.
If we check the value for the United States of America (not shown
in the table above), we find that the USA has 5.99 dentists per 10,000
population, which puts it squarely in the third percentile. Countries with
more than 8.826 dentists per 10,000—those in the top fifth—are Belgium
(11.33), Chile (14.81), Costa Rica (10.58), Israel (8.88), Lithuania (13.1),
Norway (9.29), Paraguay (12.81), and Uruguay (16.95). Of course, in-
terpreting these results is a complex matter, and results are no doubt
influenced by per capita income, number and size of accredited dental
schools, regulations for licensure and accreditation, and other infrastruc-
ture and economic factors.3
Renaming isn’t required, but it is commonplace (and this is how it’s done
in the Matplotlib documentation). We’ve seen this syntax before—using
as to give a name to an object without assignment. It’s very much like
giving a name to a file object when using the with context manager. Here
we give matplotlib.pyplot a shorter name plt so we can refer to it easily
in our code. This is almost as if we’d written
import matplotlib.pyplot
plt = matplotlib.pyplot
Almost.
Now let’s generate some data to plot. We’ll generate random numbers
in the interval (−1.0, 1.0).
import random
data = [0]
for _ in range(100):
data.append(data[-1]
+ random.random()
* random.choice([-1, 1]))
So now we’ve got some random data to plot. Let’s plot it.
plt.plot(data)
import random
import matplotlib.pyplot as plt
data = [0]
for _ in range(100):
data.append(data[-1]
+ random.random()
* random.choice([-1, 1]))
292 Data analysis and presentation
plt.plot(data)
plt.ylabel('Random numbers (cumulative)')
plt.show()
It takes only one more line to save our plot as an image file. We call
the savefig() method and provide the file name we’d like to use for
our plot. The plot will be saved in the current directory, with the name
supplied.
The basics of Matplotlib 293
import random
import matplotlib.pyplot as plt
data = [0]
for _ in range(100):
data.append(data[-1]
+ random.random()
* random.choice([-1, 1]))
plt.plot(data)
plt.ylabel('Random numbers (cumulative)')
plt.savefig('my_plot.png')
plt.show()
Flavor Servings
Cookie dough 9,214
Strawberry 3,115
Chocolate 5,982
Vanilla 2,707
Fudge brownie 6,553
Mint chip 7,005
Kale and beet 315
Let’s assume we have this saved in a CSV file called flavors.csv. We’ll
read the data from the CSV file, and produce a simple bar chart.
import csv
servings = [] # data
flavors = [] # labels
plt.bar(flavors, servings)
plt.xticks(flavors, rotation=-45)
plt.ylabel("Servings")
plt.xlabel("Flavor")
plt.tight_layout()
294 Data analysis and presentation
plt.show()
Summary
Again, this isn’t the place for a complete presentation of all the features
of Matplotlib. The intent is to give you just enough to get started. Fortu-
nately, the Matplotlib documentation is excellent, and I encourage you
to look there first for examples and help.
• https://matplotlib.org
Exceptions 295
14.5 Exceptions
StatisticsError
The statistics module has its own type of exception, StatisticsError.
You may encounter this if you try to find the mean, median, or mode of
an empty list.
>>> statistics.mean([])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/.../python3.10/statistics.py", line 328, in mean
raise StatisticsError('mean requires at least one data
point')
statistics.StatisticsError: mean requires at least one data
point
14.6 Exercises
Exercise 01
Try creating your own small data set (one dimension, five to ten elements)
and plot it. Follow the examples given in this chapter. Plot your data as
a line plot and as a bar plot.
Exercise 02
Matplotlib supports scatter plots too. In a scatter plot, each data point
is a pair of values, 𝑥 and 𝑦. Here’s what a scatter plot looks like.
296 Data analysis and presentation
Create your own data (or find something suitable on the internet) and
create your own scatter plot. 𝑥 values should be in one list, corresponding
𝑦 values should be in another list. Both lists should have the exact same
number of elements. If these are called xs and ys, then you can create a
scatter plot with
plt.scatter(xs, ys)
Make sure you display your plot, and save your plot as an image file.
Exercise 03
Edwina has calculated the mean and standard deviation of her data
set—measurements of quill length in crested porcupines (species: Hystrix
cristata). She has found that the mean is 31.2 cm and the standard
deviation is 7.9 cm.
a. If one of the quills in her sample is 40.5 cm, should she consider
this an unusually long quill? Why or why not?
b. What if there’s a quill that’s 51.2 cm? Should this be considered
unusually long? Why or why not?
Exercises 297
Exercise 04
Consider the two distributions shown, A and B.
Exercise 05
The geometric mean is another kind of mean used in statistics and fi-
nance. Rather than summing all the values in the data and then dividing
by the number of values, we take the product of all the values, and then,
if there are 𝑁 values, we take the 𝑁 th root of the result. Typically, this
is only used when all values in the data set are positive.
We define the geometric mean:
𝑁−1 1
𝑁
𝛾 = ( ∏ 𝑥𝑖 )
𝑖=0
Exception handling
• SyntaxError
• IndentationError
• AttributeError
• NameError
• TypeError
• IndexError
• ValueError
• ZeroDivisionError
• FileNotFoundError
These are exceptions defined by Python, and which are raised when
certain errors occur. (There are many, many other exceptions that are
outside the scope of this textbook.)
When an unhandled exception occurs, your program terminates. This
is usually an undesired outcome.
Here we will see that some of these exceptions can be handled grace-
fully using try and except—these go together, hand in hand. In a try
block, we include the code that we think might raise an exception. In
the following except block, we catch or handle certain exceptions. What
we do in the except block will depend on the desired behavior for your
program.
However, we’ll also see that some of these—SyntaxError and
IndentationError for example—can’t be handled, since these occur when
Python is first reading your code, prior to execution.
We’ll also see that some of these exceptions only occur when there’s a
defect in our code that we do not want to handle! These are exceptions
like AttributeError and NameError. Trying to handle these covers up de-
fects in our code that we should repair. Accordingly, there aren’t many
cases where we’d even want to handle these exceptions.
Sometimes we want to handle TypeError or IndexError. It’s very often
the case that we want to handle ValueError or ZeroDivisionError. It’s
almost always the case that we want to handle FileNotFoundError. Much
of this depends on context, and there’s a little art in determining which
exceptions are handled, and how they should be handled.
299
300 Exception handling
Learning objectives
• You will understand why many of the Python exceptions are raised.
• You will learn how to deal with exceptions when they are raised,
and how to handle them gracefully.
• You will learn that sometimes it’s not always best to handle every
exception that could be raised.
Terms introduced
• exception handling
• “it’s easier to ask forgiveness than it is to ask for permission”
(EAFP)
• “look before you leap” (LBYL)
• raise (an exception)
• try/except
15.1 Exceptions
By this time, you’ve seen quite a few exceptions. Exceptions occur when
something goes wrong. We refer to this as raising an exception.
Exceptions may be raised by the Python interpreter or by built-in
functions or by methods provided by Python modules. You may even
raise exceptions in your own code (but we’ll get to that later).
Exceptions include information about the type of exception which has
been raised, and where in the code the exception occurred. Sometimes,
quite a bit of information is provided by the exception. In general, a
good approach is to start at the last few lines of the exception message,
and work backward if necessary to see what went wrong.
There are many types of built-in exceptions in Python. Here are a few
that you’re likely to have seen before.
SyntaxError
When a module is executed or imported, Python will read the file, and
try parsing the file. If, during this process, the parser encounters a syntax
error, a SyntaxError exception is raised. SyntaxError can also be raised
when invalid syntax is used in the Python shell.
Here you see the exception includes information about the error and
where the error occurred. The ^ is used to point to a portion of code
where the error occurred.
IndentationError
IndentationError is a subtype of SyntaxError. Recall that indentation
is significant in Python—we use it to structure branches, loops, and
functions. So IndentationError occurs when Python encounters a syntax
error that it attributes to invalid indentation.
if True:
x = 1 # This should be indented!
Traceback (most recent call last):
File "/.../code.py", line 63, in runsource
code = self.compile(source, filename, symbol)
File "/.../codeop.py", line 185, in __call__
return _maybe_compile(self.compiler, source, filename,
symbol)
File "/.../codeop.py", line 102, in _maybe_compile
raise err1
File "/.../codeop.py", line 91, in _maybe_compile
code1 = compiler(source + "\n", filename, symbol)
File "/.../codeop.py", line 150, in __call__
codeob = compile(source, filename, symbol, self.flags,
True)
File "<input>", line 2
x = 1
^
IndentationError: expected an indented block
after 'if' statement on line 1
Again, almost everything you need to know is included in the last few
lines of the message. Here Python is informing us that it was expecting
an indented block of code immediately following an if statement.
AttributeError
There are several ways an AttributeError can be raised. You may have
encountered an AttributeError by misspelling the name of a method in
a module you’ve imported.
302 Exception handling
NameError
A NameError is raised when Python cannot find an identifier. For example,
if you were to try to perform a calculation with some variable x without
previously having assigned a value to x.
IndexError
Individual elements of a sequence can be accessed using an index into the
sequence. This presumes, of course, that the index is valid—that is, there
is an element at that index. If you try to access an element of a sequence
by its index, and there is no index, Python will raise an IndexError.
Exceptions 303
>>> lst = []
>>> lst[2] # There is no element at index 2
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
IndexError: list index out of range
This (above) fails because the list is empty and there is no element
at index 2. Hence, 2 is an invalid index and an IndexError is raised.
Here’s another example:
TypeError
A TypeError is raised when a value of one type is expected and a dif-
ferent type is supplied. For example, sequence indices—for lists, tuples,
strings—must be integers. If we try using a float or str as an index,
Python will raise a TypeError.
>>> len(1)
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
TypeError: object of type 'int' has no len()
ValueError
A ValueError is raised when the type of some argument or operand is
correct, but the value is not. For example, math.sqrt(x) will raise a
ValueError if we try to take the square root of a negative number.
Exceptions 305
ZeroDivisionError
Just as in mathematics, Python will not allow us to divide by zero. If
we try to, Python will raise a ZeroDivisionError. Note that this occurs
with floor division and modulus as well (as they depend on division).
>>> 10 / 0
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 10 % 0
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> 10 // 0
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
FileNotFoundError
We’ve seen how to open files for reading and writing. There are many
ways this can go wrong, but one common issue is FileNotFoundError.
This exception is raised when Python cannot find the specified file. The
file may not exist, may be in the wrong directory, or may be named
incorrectly.
306 Exception handling
open('non-existent-file')
Traceback (most recent call last):
File "/.../code.py", line 90, in runcode
exec(code, self.locals)
File "<input>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or
directory: 'non-existent-file'
while True:
n = int(input("Please enter a positive integer: "))
if n > 0:
break
This ensures that if the user enters an integer that’s less than one, that
they’ll be prompted again until they supply a positive integer. But what
happens if the naughty user enters something that cannot be converted
to an integer?
while True:
try:
user_input = input("Enter a positive integer: ")
n = int(user_input)
if n > 0:
break
except ValueError:
print(f'"{user_input}" cannot be converted to an int!')
if s in lst:
print(f'The index of "{s}" in {lst} is {lst.index(s)}.')
else:
print(f'"{s}" was not found in {lst}.')
try:
print(f'The index of "{s}" in {lst} is {lst.index(s)}.')
except ValueError:
print(f'"{s}" was not found in {lst}.')
Don’t:
15.4 Exercises
Exercise 01
ĺ Important
Be sure to save your work for this exercise, as we will revisit these
in later exercises!
>>> 1 + []
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'list'
Without using raise, write your own code that results in the following
exceptions:
310 Exception handling
a. SyntaxError
b. IndentationError
c. IndexError
d. NameError
e. TypeError
f. AttributeError
g. ZeroDivisionError
h. FileNotFoundError
Exercise 02
Now write a try/except for the following exceptions, starting with code
you wrote for exercise 01.
a. TypeError
b. ZeroDivisionError
c. FileNotFoundError
Exercise 03
SyntaxError and IndentationError should always be fixed in your code.
Under normal circumstances, these can’t be handled. NameError and
AttributeError almost always arise from programming defects. There’s
almost never any reason to write try/except for these.
Fix the code you wrote in exercise 01, for the following:
a. SyntaxError
b. IndentationError
c. AttributeError
d. NameError
Exercise 04
Usually, (but not always) IndexError and TypeError are due to program-
ming defects. Take a look at the code you wrote to cause these errors in
exercise 01. Does what you wrote constitute a programming defect? If
so, fix it.
If you believe the code you wrote constitutes a legitimate case for
try/except, write try/except for each of these.
Chapter 16
Dictionaries
Learning objectives
• You will learn how to create a dictionary.
• You will understand that dictionaries are mutable, meaning that
their contents may change.
• You will learn how to access individual values in a dictionary by
keys.
• You will learn how to iterate over dictionaries.
• You will understand that dictionary keys must be hashable.
311
312 Dictionaries
This is all well and good, but sometimes we’d like to be able to use
something other than a numeric index to access elements.
Consider conventional dictionaries which we use for looking up the
meaning of words. Imagine if such a dictionary used numeric indices to
look up words. Let’s say we wanted to look up the word “pianist.” How
would we know its index? We’d have to hunt through the dictionary to
find it. Even if all the words were in lexicographic order, it would still
be a nuisance having to find a word this way.
The good news is that dictionaries don’t work that way. We can look
up the meaning of the word by finding the word itself. This is the basic
idea of dictionaries in Python.
A Python dictionary, simply put, is a data structure which associates
keys and values. In the case of a conventional dictionary, each word is a
key, and the associated definition or definitions are the values.
Here’s how the entry for “pianist” appears in my dictionary:1
pianist n. a person who plays the piano, esp. a skilled or
professional performer
Here pianist is the key, and the rest is the value. We can write this,
with some liberty, as a Python dictionary, thus:
>>> d['pianist']
'a person who plays the piano, esp. a skilled or
professional performer'
Like lists, dictionaries are mutable. Let’s add a few more words to our
dictionary. To add a new entry to a dictionary, we can use this approach:
1
Webster’s New World Dictionary of the American Language, Second College
Edition.
Introduction to dictionaries 313
>>> d
{'pianist': 'a person who plays the piano, esp. a skilled or
professional performer', 'cicada': 'any of a family of large
flylike insects with transparent wings', 'proclivity': 'a
natural or habitual tendency or inclination, esp. toward
something discreditable', 'tern': 'any of several sea birds,
related to the gulls, but smaller, with a more slender body
and beak, and a deeply forked tail', 'firewood': 'wood used
as fuel', 'holophytic': 'obtaining nutrition by photosynthesis,
as do green plants and some bacteria'}
key value
'pianist' 'a person who plays the piano, esp. a skilled or
professional performer'
'cicada' 'any of a family of large flylike insects with
transparent wings'
'proclivity' 'a natural or habitual tendency or inclination,
esp. toward something discreditable'
'tern' 'any of several sea birds, related to the gulls,
but smaller, with a more slender body and beak, and
a deeply forked tail'
'firewood' 'wood used as fuel'
'holophytic' 'obtaining nutrition by photosynthesis, as do green
plants and some bacteria'
>>> d['tern']
'any of several sea birds, related to the gulls, but smaller,
with a more slender body and beak, and a deeply forked tail'
If we try to access a key which does not exist, this results in a KeyError.
314 Dictionaries
>>> d['bungalow']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'bungalow'
So far, in the examples above, keys and values have been strings. But
this needn’t be the case.
There are constraints on the kinds of things we can use as keys, but
almost anything can be used as a value.
Here are some examples of valid keys:
>>> students['aftoure']['major']
'music'
Restrictions on keys
Keys in a dictionary must be hashable. In order for an object to be
hashable, it must be immutable, or if it is an immutable container of
other objects (e.g., a tuple) then all the objects contained must also be
immutable. Valid keys include objects of type int, float (OK, but a little
strange), str, bool (also OK, but use cases are limited). Tuples can also
serve as keys as long as they do not contain any mutable objects.
>>> d = {True: 'if porcupines are blue, then the sky is pink',
... False: 'chunky monkey is the best ice cream'}
Testing membership
Just as with lists we can use the keyword in to test whether a particular
key is in a dictionary.
Dictionaries have three different view objects: items, keys, and values.
Dictionaries have methods that return these view objects:
Note that it’s common to exclude .keys() if it’s keys you want, since
the default behavior is to iterate over keys (as shown in the previous
example).
Some examples
Let’s say we wanted to count the number of pieces of furniture in our
dwelling.
>>> count = 0
>>> for lst in furniture.values():
... count = count + len(lst)
...
>>> count
10
Let’s say we wanted to find all the students in the class who are not
CS majors, assuming the items in our dictionary look like this:
>>> students =
... {'esmerelda' : {'class': 2024, 'major': 'ENSC', 'gpa': 3.08},
... 'winston': {'class': 2023, 'major': 'CS', 'gpa': 3.30},
... 'clark': {'class': 2022, 'major': 'PHYS', 'gpa': 2.95},
... 'kumiko': {'class': 2023, 'major': 'CS', 'gpa': 3.29},
... 'abeba' : {'class': 2024, 'major': 'MATH', 'gpa': 3.71}}
One approach:
>>> non_cs_majors = []
>>> for student, info in students.items():
... if info['major'] != 'CS':
... non_cs_majors.append(student)
...
>>> non_cs_majors
['esmerelda', 'clark', 'abeba']
Dictionaries are mutable, and thus, like lists, they can be changed.
Dictionaries also support .pop() but it works a little differently than it
does with lists. The .pop() method for dictionaries requires a valid key
as an argument. This is because dictionaries don’t have the same sense
of linear order as a list—everything is based on keys.
So this works:
Python also provides the keyword del which can be used to remove a
key from a dictionary.
But be careful! If you do not specify a key, the entire dictionary will
be deleted!
Notice also that .pop() with a key supplied will return the value
associated with that key and then remove the key/value pair. del will
simply delete the entry.
16.4 Hashables
The keys of a dictionary cannot be arbitrary Python objects. In order to
serve as a key, an object must be hashable.
Without delving into too much technical detail, the reason is fairly
straightforward. We can’t have keys that might change!
320 Dictionaries
>>> x = 2
>>> hash(x)
2
>>> x = 4.11
>>> hash(x)
253642731013507076
>>> x = 'hello'
>>> hash(x)
1222179648610370860
>>> x = True
>>> hash(x)
1
>>> x = (1, 2, 3)
>>> hash(x)
529344067295497451
>>> x = [1, 2, 3]
>>> hash(x)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
Now, a tuple can contain a tuple, which may contain another tuple
and so on. All it takes is one immutable element, no matter how deeply
nested, to make an object unhashable.
2
William Shakespeare, The Merchant of Venice, Act V, Scene I (Portia).
322 Dictionaries
d = {}
for char in s.lower():
try:
d[char] += 1
except KeyError:
d[char] = 1
16.6 Exceptions
KeyError
If you try to read or pop or delete a key from a dictionary which does
not exist, a KeyError is raised. This is similar to the IndexError you’ve
seen in the cases of lists and tuples.
If you encounter a KeyError it means the specified key does not exist
in the dictionary.
Exercises 323
TypeError
If you try to add to a dictionary a key which is not hashable, Python
will raise a type error:
16.7 Exercises
Exercise 01
Create a dictionary for the following data:
c .What are the types for student, netid, major, and courses?
324 Dictionaries
Exercise 02
Now that you’ve created the dictionary in Exercise 01, write queries for
the following. A query retrieves selected data from some data structure.
Here the data structure is the dictionary created in exercise 01. Some
queries may be one-liners. Some queries may require a loop.
Exercise 03
Dictionary keys must be unique. We cannot have duplicate keys in a
dictionary.
What do you think happens if we were to enter this into the Python
shell?
Exercise 04
Create this dictionary:
d = {'foo': 'bar'}
Exercise 05
Write a function that takes a string as an argument and returns a dic-
tionary containing the number of occurrences of each character in the
string.
Exercise 06
a. Write a function that takes an arbitrary dictionary and returns the
keys of the dictionary as a list.
b. Write a function that takes an arbitrary dictionary and returns the
values of the dictionary as a list.
Chapter 17
Graphs
Learning objectives
• You will learn some of the terms associated with graphs.
• You will learn how to represent data using a graph.
• You will learn about searching a graph using breadth-first search.
Terms introduced
• graph
• vertices (nodes)
• edge
• neighbor (common edge)
• adjacent
• breadth-first search
325
326 Graphs
ROUTES = {
'Burlington': ['St Albans', 'Montpelier', 'Middlebury'],
'Montpelier': ['Burlington', 'White River Junction',
'St Johnsbury'],
'White River Junction': ['Montpelier', 'Brattleboro',
'St Johnsbury'],
'Brattleboro': ['White River Junction'],
'Newport': ['St Johnsbury'],
'St Albans': ['Burlington', 'Swanton'],
'St Johnsbury': ['Montpelier', 'Newport',
'White River Junction'],
'Swanton': ['St Albans'],
'Middlebury': ['Burlington', 'Rutland'],
'Rutland': ['Middlebury', 'Bennington'],
'Bennington': ['Rutland']
}
At some point, the queue is exhausted (once we’ve popped the last
one off), and we only add unvisited towns to the queue. So this algorithm
will terminate.
328 Graphs
Once the algorithm has terminated, we have a list of the visited towns,
in the order they were visited.
Needless to say, this isn’t a very sophisticated approach. For example,
we don’t consider the distances traveled, and we have a very simple graph.
But this suffices for a demonstration of BFS.
A worked example
Here’s a complete, worked example of breadth-first search. It might help
for you to go over this while checking the map (above).
Say we choose St Johnsbury as a starting point. Thus, the list of
visited towns will be St Johnsbury, and the queue will contain only St
Johnsbury. Then, in our while loop…
First, we pop St Johnsbury from the front of the queue, and we check
its neighbors. Its neighbors are Montpelier, Newport, and White River
Junction so we append Montpelier, Newport, and White River Junction
to the list of visited towns, and to the queue. At this point, the queue
looks like this:
At the next iteration, we pop Montpelier from the front of the queue.
Now, when we check Montpelier’s neighbors, we find Burlington, White
River Junction, and St Johnsbury. St Johnsbury has already been visited
and so has White River Junction, so we leave them be. However, we have
not visited Burlington, so we append it to the list of visited towns and
to the queue. At this point, the queue looks like this:
At the next iteration, we pop Newport from the front of the queue.
We check Newport’s neighbors and find only St Johnsbury, so there’s
nothing to append to the queue. At this point, the queue looks like this:
At the next iteration we pop White River Junction from the front
of the queue. White River Junction is adjacent to Montpelier (already
visited), Brattleboro, and St Johnsbury (already visited). So we append
Brattleboro to visited list and to the queue. At this point, the queue
looks like this:
['Burlington', 'Brattleboro']
At the next iteration, we pop St Albans from the front of the queue.
We check St Alban’s neighbors. These are Burlington (already visited)
and Swanton. So we append Swanton to the visited list and to the queue.
At this point, the queue looks like this:
['Middlebury', 'Swanton']
['Swanton', 'Rutland']
At the next iteration, we pop Swanton from the front of the queue.
Swanton’s only neighbor is St Albans and that’s already visited so we
do nothing. At this point, the queue looks like this:
['Rutland']
At the next iteration we pop Rutland from the front of the queue.
Rutland’s neighbors are Bennington and Middlebury. We’ve already vis-
ited Middlebury, but we haven’t visited Bennington, so we add it to the
list of visited towns and to the queue. At this point, the queue looks like
this:
['Bennington']
[]
Applications
Search algorithms on graphs have abundant applications, including solv-
ing mazes, and games like tic-tac-toe and various board games.
Supplemental reading
• https://en.wikipedia.org/wiki/Breadth-first_search
• https://en.wikipedia.org/wiki/Depth-first_search
17.3 Exercises
Exercise 01
Take a look at this dictionary representing a graph (using adjacency list
representation):
FRIENDS = {
'Alessandro': ['Amelia'],
'Amelia': ['Sofia', 'Emma', 'Daniel', 'Alessandro'],
'Ava': ['Mia'],
'Sofia': ['Amelia', 'Selim', 'Olivia'],
'Daniel': ['Amelia', 'Emma', 'Selim', 'Olivia'],
'Emma': ['Amelia', 'Daniel', 'Selim'],
'Selim': ['Sofia', 'Daniel', 'Emma', 'Ethan'],
'Olivia': ['Sofia', 'Ethan', 'Daniel'],
'Ethan': ['Olivia', 'Selim'],
'Amara': ['Isabella', 'Benjamin'],
'Benjamin': ['Amara', 'Isabella'],
'Isabella': ['Amara', 'Benjamin'],
'Mia': ['Ava', 'James'],
'James': ['Mia']
}
Exercise 02
Consider this graph:
Exercise 03 (challenge!)
In the case of undirected graphs (the only kind shown in this text) edges
do not have a direction, and thus if A is connected to B then B is con-
nected to A. We see this in the adjacency list representation: if Selim is
a friend of Emma, then Emma is a friend of Selim.
Write a function which takes the adjacency list representation of any
arbitrary graph and returns True if this symmetry is correctly represented
in the adjacency list representation and False otherwise. Such a function
could be used to validate an encoding of an undirected graph.
Appendix A
Glossary
absolute value
The absolute value of a number is its distance from the origin (0), that
is, the magnitude of the number regardless of its sign. In mathematics
this is written with vertical bars on either side of the number or variable
in question. Thus
|4| = 4
| − 4| = 4
and generally,
𝑥 𝑥≥0
|𝑥| = {
−𝑥 𝑥 < 0.
The absolute value of any numeric literal, variable, or expression can be
calculated with the Python built-in, abs().
accumulator
An accumulator is a variable that’s used to hold a cumulative result
within a loop. For example, we can calculate the sum of all numbers
from 1 to 10, thus
s = 0 # s is the accumulator
for n in range(1, 11):
s += n # s is incremented at each iteration
adjacent
We say two vertices A, B, in a graph are adjacent if an edge exists in the
graph with endpoints A and B.
alternating sum
An alternating sum is a sum which alternates addition and subtraction
depending on the index or parity of the term to be summed. For example:
333
334 Glossary
s = 0
for n in range(1, 11):
if n % 2: # n is odd
s -= n # subtract n
else: # n must be even
s += n # add n
argument
An argument is a value (or variable) that is passed to a function when
it is called. An argument is then assigned to the corresponding formal
parameter in the function definition. For example, if we call Python’s
built-in sum() function, we must provide an argument (the thing that’s
being summed).
s = sum([2, 3, 5, 7, 11])
Here, the argument supplied to the sum() function is the list [2, 3, 5,
7, 11]. See: formal parameter.
Most arguments that appear in this text are positional arguments, that
is, the formal parameters to which they are assigned depend on the order
of the formal parameters in the function definition. The first argument
is assigned to the first formal parameter (if any). The second argument
is assigned to the second formal parameter, and so on. The number of
positional arguments must agree with the number of positional formal
parameters, otherwise a TypeError is raised.
See also: keyword argument.
arithmetic mean
The arithmetic mean is what’s informally referred to as the average of a
set of numbers. It is the sum of all the numbers in the set, divided by
the number of elements in the set. Generally,
1 𝑁−1
𝜇= ∑𝑥.
𝑁 𝑖=0 𝑖
This can be implemented in Python:
m = sum(x) / len(x)
arithmetic sequence
An arithmetic sequence of numbers is one in which the difference between
successive terms is a constant. For example, 1, 2, 3, 4, 5,… is an arithmetic
sequence because the difference between successive terms is the constant,
1. Other examples of arithmetic sequences:
2, 4, 6, 8, 10, …
7, 10, 13, 16, 19, 22, …
44, 43, 42, 41, 40, 39, …
range(3, 40, 3)
assertion
An assertion is a statement about something you believe to be true. “At
the present moment, the sky is blue.” is an assertion (which happens to
be true where I am, as I write this). We use assertions in our code to
help verify correctness, and assertions are often used in software testing.
Assertions can be made using the Python keyword assert (note that this
is a keyword and not a function). Example:
assert 4 == math.sqrt(16)
assignment
We use assignment to bind values to names. In Python, this is accom-
plished with the assignment operator, =. For example:
x = 4
creates a name x (presuming x has not been defined earlier), and asso-
ciates it with the value, 4.
binary code
Ultimately, all instructions that are executed on your computer and all
data represented on your computer are in binary code. That is, as se-
quences of 0s and 1s.
Boolean connective
The Boolean connectives in Python are the keywords and, or, and not.
See: Boolean expressions, and Chapter 8 Branching.
Boolean expression
A Boolean expression is an expression with one or more Boolean variables
or literals (or variables or literals with truth value), joined by zero or
more Boolean connectives. The Boolean connectives in Python are the
keywords and, or, and not. For example:
True
branching
It is through branching that we implement conditional execution of por-
tions of our code. For example, we may wish some portion of our code
to be executed only if some condition is true. Branching in Python is
implemented with the keywords if, elif, and else. Examples:
if x > 1:
print("x is greater than one")
and
if x < 0:
print("x is less than zero")
elif x > 1:
print("x is greater than one")
else:
print("x must be between 0 and 1, inclusive")
bug
Simply put, a bug is a defect in our code. I hesitate to call syntax errors
“bugs”. It’s best to restrict this term to semantic defects in our code (and
perhaps unhandled exceptions which should be handled). In any event,
when you find a bug, fix it!
bytecode
Bytecode is an intermediate form, between source code (written by hu-
mans) and binary code (which is executed by your computer’s central
processor unit (CPU)). Bytecode is a representation that’s intended to be
efficiently executed by an interpreter. Python, being an interpreted lan-
guage, produces an intermediate bytecode for execution on the Python
virtual machine (PVM). Conversion of Python source code to intermedi-
ate bytecode is performed by the Python compiler.
camel case
Camel case is a naming convention in which an identifier composed of
multiple words has the first letter of each interior word capitalized. Some-
times stylized thus: camelCase. See: PEP 8 for appropriate usage.
central tendency
In statistics, the arithmetic mean is one example of a measure of central
tendency, measuring a “central” or (hopefully) “typical” value for a data
set. Another aspect of central tendency is that values tend to cluster
around some central value (e.g., mean).
comment
Strictly speaking, comments are text appearing in source code which is
not read or interpreted by the language, but instead, is solely for the
benefit of human readers. Comments in Python are delimited by the
octothorpe, #, a.k.a. hash sign, pound sign, etc. In Python, any text on
a given line following this symbol is ignored by the interpreter.
In general, it is good practice to leave comments which explain why
something is as it is. Ideally, comments should not be necessary to explain
how something is being done (as this should be evident from the code
itself).
comparable
Objects are comparable if they can be ordered or compared, that is, if
the comparison operators—==, <=, >=, <, >, and != can be applied. If so,
the operands on either side of the comparison operators are comparable.
Instances of many types can be compared among themselves (any string
is comparable to all other strings, any numeric is comparable to all other
numerics), but some types cannot be compared with other objects of
the same type. For example, we cannot compare objects of type range
or function this way. In most cases, objects of different types are not
comparable (with different numeric types as a notable exception). For
example, we cannot compare strings and integers.
compiler
A compiler is a program which converts source code into machine code,
or, in the case of Python, to bytecode, which can then be run on the
Python virtual machine.
concatenation
Concatenation is a kind of joining together, not unlike coupling of railroad
cars. Some types can be concatenated, others cannot. Strings and lists
are two types which can be concatenated. If both operands are a string,
or both operands are a list, then the + operator performs concatenation
(rather than addition, if both operands were numeric).
339
Examples:
condition
Conditions are used to govern the behavior of while loops and if/elif/else
statements. Loops or branches are executed when a condition is true—
that is, it evaluates to True or has a truthy value. Note: Python supports
conditional expressions, which are shorthand forms for if/else, but these
are not presented in this text.
congruent
In the context of modular arithmetic, we say two numbers are congruent
if they have the same remainder with respect to some given modulus.
For example, 5 is congruent to 19 modulo 2, because 5 % 2 leaves a
remainder of 1 and 19 % 2 also leaves a remainder of 1. In mathematical
notation, we indicate congruence with the ≡ symbol, and we can write
this example as 5 ≡ 19 (mod 2). In Python, the expression 5 % 2 == 19
% 2 evaluates to True.
console
Console refers either to the Python shell, or Python input/output when
run in a terminal.
constructor
A constructor is a special function which constructs and returns an ob-
ject of a given type. Python provides a number of built-in constructors,
e.g., int(), float(), str(), list(), tuple(), etc. These are often used in
Python to perform conversions between types. Examples:
context manager
Context managers are created using the with keyword, and are commonly
used when working with file I/O. Context managers take over some of
the work of opening and closing a file, or ensuring that a file is closed
at the end of a with block, regardless of what else might occur within
that block. Without using a context manager, it’s up to the programmer
to ensure that a file is closed. Accordingly, with is the preferred idiom
whenever opening a file.
Context managers have other uses, but these are outside the scope of
this text.
delimiter
A delimiter is a symbol or symbols used to set one thing apart from
another. What delimiters are used, how they are used, and what they
delimit depend on context. For example, single-line and inline comments
in Python are delimited by the # symbol at the start and the newline
character at the end. Strings can be delimited (beginning and end) with
apostrophes, ', quotation marks, ", or triple quotation marks, """. In a
CSV file, columns are delimited by commas.
dictionary
A dictionary is a mutable type which stores data in key/value pairs, like
a conventional dictionary. Keys must be unique, and each key is associ-
ated with a single value. Dictionary values can be just about anything,
including other dictionaries, but keys must be hashable. Dictionaries are
written in Python using curly braces, with colons used to separate keys
and values. Dictionary entries (elements) are separated by commas.
Examples:
dividend
In the context of division (including floor division, // and the modulo
operator, %), the dividend is the number being divided. For example, in
the expression 21 / 3, the dividend is 21.
divisor
In the context of division (including floor division, // and the modulo op-
erator, %), the divisor is the number dividing the dividend. For example,
in the expression 21 / 3, the divisor is 3. In the context of the modulo
operator and modular arithmetic, we also refer to this as the modulus.
driver code
Driver code is an informal way of referring to the code which drives
(runs) your program, to distinguish it from function definitions, constant
assignments, or classes defined by the programmer. Driver code is often
fenced behind the if statement: if __name__ == '__main__':
docstring
A docstring is a triple-quoted string which includes information about
a program, module, function, or method. These should not be used for
inline comments. Unlike inline comments, docstrings are read and parsed
by the Python interpreter. Example at the program (module) level:
"""
My Fabulous Program
Egbert Porcupine <eporcupi@uvm.edu>
This program prompts the user for a number and
returns the corresponding frombulation coefficient.
"""
def successor(n_):
"""Given some number, n_, returns its successor. """
return n_ + 1
dunder
Special variables and functions in Python have identifiers that begin
and end with two underscores. Such identifiers are referred to as dun-
ders, a descriptive portmanteau of “double underscore.” Examples include
the variable __name__ and the special name '__main__', and many other
identifiers defined by the language. These are generally used to indicate
visually that these are system-defined identifiers.
342 Glossary
edge (graph)
A graph consists of vertices (also called nodes) and edges. An edge con-
nects two vertices, and each edge in a graph has exactly two endpoints
(vertices). All edges in graphs in this text are undirected, meaning that
they have no direction. If an edge exists between vertices A and B, then
A is adjacent to B and B is adjacent to A.
empty sequence
The term empty applies to any sequence which contains no elements. For
example, the empty string "", the empty list [], and the empty tuple
(,)—these are all empty sequences. We refer to these using the definite
article (“the”), because there is only one such object of each type. Of
note, empty sequences are falsey.
entry point
The entry point of a program is the point at which code begins execution.
In Python, the dunder __main__ is the name of the environment where
top-level code is run, and thus indicates the entry point. Example:
"""
Some docstring here
"""
def square(x_):
return x_ * x_
escape sequence
An escape sequence (within a string) is a substring preceded by a \ char-
acter, indicating that the following character should be interpreted liter-
ally and not as a delimiter. Example: print('The following apostrophe
isn\'t a delimiter')
Escape sequences are also used to represent special values that other-
wise can’t be represented within a string. For example, \n represents a
new line and \t represents a tab.
Euclidean division
This is the kind of division you first learned in primary school, before
you learned about decimal expansion. A calculation involving Euclid-
ian division yields a quotient and a remainder (which may be zero). In
Python, this is implemented by two separate operators, one which yields
the quotient (without decimal expansion), and the other which yields
the remainder. These are // (a.k.a. floor division) and % (a.k.a., modulo)
respectively.
So, for example, in primary school, when asked to divide 25 by 3,
you’d answer 8 remainder 1. In Python:
evaluate / evaluation
Expressions are evaluated by reducing them to a value. For example,
the evaluation of the expression 1 + 1 yields 2. The evaluation of larger
expressions proceeds by order of operator precedence or order of function
calls. So, for example,
exception
An exception is an error that occurs at run time. This occurs when code
is syntactically valid, but it contains semantic defects (bugs) or receives
unexpected values. When Python detects such an error, it raises an ex-
ception. If the exception is not handled, program execution terminates.
344 Glossary
exception handling
Exception handling allows the programmer to anticipate the possibility
of certain exceptions that might arise during execution and provide a
fallback or means of responding to the exception should it arise. For
example, we often use exception handling when validating input from
the user.
while True:
response = input("Enter a valid number greater than zero: ")
try:
x = float(response)
if x > 0:
break
except ValueError:
print(f"Sorry I cannot convert {response} to a float!")
expression
An expression is any syntactically valid code that can be evaluated, that
is, an expression yields a value. Expressions can be simple (e.g., a single
literal) or complex (i.e., composed of literals, variables, operators, func-
tion calls, etc). Expressions are to be distinguished from statements—
expressions have an evaluation, statements do not.
falsey
When used as conditions or in Boolean expressions, most everything in
Python has a truth value. If something has a truth value that is treated
as if it were false, we say such a thing is falsey. Falsey things include
(but are not limited to) empty sequences, 0, and 0.0.
Fibonacci sequence
The Fibonacci sequence is a sequence of natural numbers starting with
0, 1, in which each successive element is the sum of the two preceding ele-
ments. Thus the Fibonacci sequence begins 0, 1, 1, 2, 3, 5, 8, 13, 21, …
The Fibonacci sequence gets its name from Leonardo of Pisa (c. 1170–c.
1245 CE) whose nickname was Fibonacci. The Fibonacci sequence was
known to Indian mathematicians (notably Pingala) as early as 200 BCE,
but history isn’t always fair about naming these things.
345
file
A file is a component of a file system that’s used to store data. The
computer programs you write are saved as files, as are all the other doc-
uments, programs, and other resources you may have on your computer.
Files are contained in directories.
floating-point
A floating-point number is a number which has a decimal point. These
are represented differently from integers. Python has a type, float which
is used for floating-point numbers.
floor division
Floor division calculates the Euclidean quotient, or, if you prefer, the
largest integer that is less than or equal to the result of floating-point
division. For example, 8 // 3 yields 2, because 2 is the largest integer
less than 2.666…(which is the result of 8 / 3).
floor function
The floor function returns the largest integer less than or equal to the
argument provided. In mathematical notation, we write
⌊𝑥⌋
and, for example ⌊6.125⌋ = 6.
Python implements this function in the math module. Example:
math.floor(6.125) yields 6.
flow chart
A flow chart is a tool used to represent the possible paths of execution
and flow of control in a program, function, method, or portion thereof.
See: Chapter 8 Branching.
format specifier
Format specifiers are used within f-string replacement fields to indicate
how the interpolated content should be formatted, e.g., indicating pre-
cision of floating-point numbers; aligning text left, right or center, etc.
There is a complete “mini-language” of format specifiers. Within the re-
placement field, the format specifier follows the interpolated expression,
separated by a colon. Format specifiers are optional.
f-string
f-string is short for formatted string literal. f-strings are used for string in-
terpolation, where values are substituted into replacement fields within
the f-string (with optional formatting specifiers). The syntax for an f-
string requires that it be prefixed with the letter “f”, and replacement
fields appear within curly braces. For example, f"The secret number is
{num}." In this case, the replacement field is {num}, and the string rep-
resentation of the value associated with the identifier num is substituted
into the string. So, if we had num = 42, then this string becomes “The
secret number is 42.”
function
In Python, a function is a named block of code which performs some
task or returns some value. We distinguish between the definition of a
function, using the keyword def, and calls to the function, which result in
the function being executed and then returning to the point at which it
was called. Functions may have zero or more formal parameters. Formal
parameters receive arguments when the function is called.
All Python functions return a value (the default, if there is no explicit
return statement, is for the function to return None).
See: Chapter 5 Functions.
graph
A graph consists of a set of vertices and a set of edges. Trivially, the
empty graph is one with no vertices and thus no edges. A graph with
two or more vertices may include edges (assuming we exclude self-edges
which is common). An edge connects two vertices.
Graphs are widely used in computer science to represent networks of
all kinds as well as mathematical and other objects.
hashable
Dictionary keys must be hashable. Internally, Python produces a hash
(a number) corresponding to each key in a dictionary and uses this to
look up elements in the dictionary. In order for this to work, such hashes
must be stable—they may not change. Accordingly, Python disallows the
use of non-hashable objects as dictionary keys. This includes mutable
objects (lists, dictionaries) or immutable containers of mutable objects
(for example, a tuple containing a list). Most other objects are hashable:
integers, floats, strings, etc.
347
heterogeneous
Heterogeneous means “of mixed kind or type”. Python allows for heteroge-
neous lists or tuples, meaning that these can contain objects of different
types. For example, this is just fine in Python: ['a', 42, False, [x],
101.9].
identifier
An identifier is a name we give to an object in Python. For many types,
we give a name by assignment. Example:
x = 1234567
gives the value 1234567 the name (identifier) x. This allows us to refer
to the value 1234567 in our code by its identifier, x.
print(x)
z = x // 100
# etc
def successor(n):
return n + 1
immutable
If an object is immutable it means that the object cannot be changed.
In Python, int, float, str, and tuple are all immutable types. It is true
that we can reassign a new value to a variable, but this is different from
changing the value itself.
import
We import a module for use in our own code by using Python’s import
keyword. For example, if we wish to use the math module, we must first
import it thus:
import math
impure functions
Impure functions are functions with side effects. Side effects include read-
ing from or writing to the console, reading from or writing to a file, or
mutating (changing) a non-local variable. Cf., pure function.
348 Glossary
incremental development
Incremental development is a process whereby we write, and presumably
test, our programs in small increments.
index / indices
All sequences have indices (the plural of index). To each element in the
sequence, there corresponds an index. We can use an index to access
an individual element within a sequence. For this we use square bracket
notation (which is distinct from the syntax used to create a list). For
example, given the list
>>> lst[0]
'dog'
>>> lst[2]
'gerbil'
>>> lst[1]
'cat'
But this won’t work with tuples or strings (since they are immutable).
I/O
I/O is short for input/output. This may refer to console I/O, file I/O, or
some other form of input and output.
instance / instantiate
An instance is an object of a given type once created. For example, 1 is
an instance of an object of type int. More often, though, we speak of
instances as objects returned by a constructor. For example, when using
the csv module, we can instantiate CSV reader and writer objects by
calling the corresponding constructors, csv.reader() and csv.writer().
What are returned by these constructors are instances of the correspond-
ing type.
syntax highlighting, hints, and other facilities for writing, testing, de-
bugging, and running code. Python provides its own IDE, called IDLE
(integrated development and learning environment) and there are many
commercial or open source IDEs: Thonny (worth a look if you’re a be-
ginner), JetBrains Fleet, JetBrains PyCharm, Microsoft VS Code, and
many others.
interactive mode
Interactive mode is what we’re using when we’re interacting at the
Python shell. At the shell, you’ll see the Python prompt >>>. Work at the
shell is not saved—so it’s suitable for experimenting but not for writing
programs.
Compare with script mode, below.
interpolation
In this text, when we mention interpolation, we’re always talking about
string interpolation (and not numeric interpolation or frame interpola-
tion).
See: string interpolation.
interpreter
An interpreter is a program that executes some form of code without it
first being compiled into binary machine code. In Python, this is done
by the Python interpreter, which interprets bytecode produced by the
Python compiler.
iterable / iterate
An iterable object is one we may iterate, that is, it is an object which
contains zero or more elements, which are ordered and can be taken one
at a time. A familiar form of iteration is dealing from a deck of cards.
The deck contains zero or more elements (52 for a standard deck). The
deck is ordered—which doesn’t mean the cards are sorted, it just means
that each card has a unique position within the deck (depending on how
the deck is shuffled). If we were to turn over the cards from the top of
the deck one at a time, until there were no more cards left, we’d have
iterated over the deck. At the first iteration, we might turn over the six of
clubs. At the second iteration, we might turn over the nine of diamonds.
And so on. That’s iterating an iterable.
Iterables in Python include objects of type str, list, tuple, dict,
range, and enumerate (there are others as well). When we iterate these
objects, we get one element at a time, in the order they appear within
350 Glossary
the object.1 When we iterate in a loop, unless there are specific exit
conditions (i.e., return or break) iteration continues until the iterable is
exhausted (there are no more elements remaining to iterate). Going back
to the example of our deck of cards, once we’ve turned over the 52nd
card, we’ve exhausted the deck, there’s nothing left to turn over, and
iteration ceases.
keyword
Certain identifiers are reserved by the syntax of any language as key-
words. Keywords always have the same meaning and cannot be changed
by the user. Python keywords we’ve seen in this text are: False, None,
True, as, assert, break, def, del, elif, else, except, for, if, import, in,
not, or, pass, return, try, while, and with. Feel free to try redefining any
of these at the Python shell—you won’t succeed.
keyword argument
A keyword argument is an argument provided to a function which is
preceded by the name. (Note: keyword arguments have absolutely noth-
ing to do with Python keywords.) Keyword arguments are specified in
the function definition. Defining functions with keyword arguments is
not covered in this text, but there are a few instances of using keyword
arguments, notably:
1. The optional end keyword argument to the print() function, which
allows the user to override the default ending of printed strings
(which is the newline character, \n).
2. The optional newline keyword argument to the open() function
(which is a little hack to prevent ugly behavior on certain Windows
machines).
Keyword arguments, if any, always follow positional arguments.
lexicographic order
Lexicographic order is how Python orders strings and certain other type
by default when sorting. This is, essentially, how strings would appear
if in a conventional dictionary. For example, when sorting two words,
the first letters are compared. If the first letters are different, then the
word which contains the first letter which appears earlier in the alphabet
appears before the other in the sort. If the first letters are the same, then
the second letters are compared, and so on. If the number of letters is
different, but all letters that can be compared are the same, then the
shorter word appears before the other in the sort. So, for example the
word 'sort' would appear before the word 'sorted' in a sorted list of
words.
1
Dictionaries are a bit of a special case here, since the order in which elements
are added to a dictionary is not necessarily the order in which they appear when
iterating, but there is an underlying order. Moreover, Python does provide an Or-
deredDict type (provided by the collections module) which preserves the order of
addition when iterating. But these are minor points.
351
list
A list (type list) is a mutable container for other objects. Lists are
sequences, meaning that their contents are ordered (each object in the
list has a specific position within the list). Lists are iterable, meaning
that we can iterate over them in a for loop. We can create a list by
assigning a list literal to a variable:
or we may create a list from some other iterable object using the list
constructor:
literal
A literal is an actual value of a given type. 1 is a literal of the int type.
'muffin' is a literal of the str type. ['foo', 'bar', 123] is a literal of
the list type. During evaluation, literals evaluate to themselves.
local variable
A local variable is one which is defined within a limited scope. The local
variables we’ve seen in this text are those created by assignment within
a function.
loop
A loop is a structure which is repeated zero or more times, depending
on the condition if it’s a while loop, or depending on the iterable being
iterated if it’s a for loop. break and (in some cases) return can be used
to exit a loop which might otherwise continue.
Python supports two kinds of loops: while loops which continue to
execute as long as some condition is true, and for loops which iterate
some iterable.
Matplotlib
Matplotlib is a widely used library for creating plots, animations, and
other visualizations of data in Python. It is not part of the Python stan-
dard library, and so it must be installed before it can be used.
For more, see: https://matplotlib.org.
352 Glossary
method
A method is a function which is bound to objects of a specific type.2 For
example, we have list methods such as .append(), .pop(), and .sort(),
string methods such as .upper(), .capitalize(), and .strip(), and so
on. Methods are accessed by use of the dot operator (as indicated in the
identifiers above). So to sort a list, lst, we use lst.sort(); to remove and
return the last element from a non-empty list, lst, we use lst.pop(); to
return a capitalized copy of a string, s, we use s.capitalize(); and so
on.
module
A module is a collection of Python objects and code. Examples include
the math module, the csv module, and the statistics module. In order to
use a module, it must be imported, e.g., import math. Imported modules
are given namespaces, and we access functions within a module using
the dot operator. For example, when importing the math module, the
namespace is math and we access a function within that namespace thus:
math.sqrt(2), as one example.
You can import programs you write yourself if you wish to reuse
functions written in another program. In cases like this, your program
can be imported as a module.
modulus
When using the modulo operator, we refer to the second operand as the
modulus. For example, in the case of 23 % 5 the modulus is 5.
See also: Euclidean division and congruent.
Monte Carlo
Monte Carlo method makes use of repeated random sampling (from some
distribution), in order to solve certain classes of problems. For example,
we can use the Monte Carlo method to approximate 𝜋. The Monte Carlo
method is widely used in physics, economics, operations management,
and many other domains.
mutable
An object (type) is mutable if it can be changed after it’s been created.
Lists and dictionaries are mutable, whereas objects of type int, float,
str and tuple are not.
namespace
Namespaces are places where Python objects are stored, and these are
very much like but not identical to dictionaries. For example, like dictio-
nary keys, identifiers within a namespace are unique (you can’t have two
different variables named x in the same namespace).
Most often you’re working in the global namespace. However, func-
tions, and modules you import have their own namespaces. In the case of
functions, a function’s namespace is created when the function is called,
and destroyed when the function returns. In the case of modules, we re-
fer to elements within a module’s namespace using the dot operator (see:
Module).
object
Pretty much anything in Python that’s not a keyword or operator or
punctuation is an object. Every object has a type, so we have objects of
type int, objects of type str, and so on. Functions are objects of type
function.
If you learn about object-oriented programming you’ll learn how to
define your own types, and instantiate objects of those types.
operator
An operator is a special symbol (or combination of symbols) that takes
one or more operands and performs some operation such as addition,
multiplication, etc., or some kind of comparison. Operators include (but
are not limited to) +, -, *, **, /, //, %, =, ==, > <, >=, <=, and !=. Oper-
ators that have a single operand are called unary operators, e.g., unary
negation. Operators that take two operands are called binary operators.
Some operators perform different operations depending on the type
of their operands. This is called operator overloading. Example: If both
operands are numeric, + performs addition; if both operands are strings,
or both operands are lists, + performs concatenation.
def square(x):
return x * x
y = square(12)
y = square(some_variable)
The examples above show function calls supplying arguments which are
assigned to the formal parameter in the function definition.
Formal parameters exist only within a functions namespace (which is
destroyed upon return). See: namespace.
PEP 8
PEP 8 is the official Python style guide. See: https://peps.python.org/
pep-0008/
pseudo-random
It is impossible for a computer to produce a truly random number (what-
ever that might actually be). Instead, they can produce pseudo-random
numbers which appear random and approximate certain distributions.
Pseudo-random number generation is implemented in Python’s random
module. See: Chapter 12 Randomness, games, and simulations, for more.
pure function
A pure function is a function without side effects. Furthermore, the out-
put of a pure function (the value returned), depends solely on the argu-
ment(s) supplied and the function definition, and given the same argu-
ment a pure function will always return precisely the same result. In this
regard, pure functions are akin to mathematical functions.
Cf. impure function.
quantile
In statistics, a quantile is a set of points which divide a distribution or
data set into intervals of equal probability. For example, if we divide our
data into quartiles (a quantile of four parts), then each of the four parts
has equal probability. Note that if dividing into 𝑛 parts we need 𝑛 − 1
values to do so.
Some quantiles have special names. For example, we call the value that
divides a distribution, sample or population into two equally probable
parts a median. If we divide into 100 parts we call that percentiles.
quotient
A quotient is the result of division, whether floor division or floating-
point division. In the case of / and //, the value yielded is called the
quotient.
random walk
A random walk is a mathematical object which describes a path in some
space (say, the integers) taken by a repeated random sequence of steps.
355
remainder
The remainder is the quantity left over after performing Euclidean (floor)
division. The remainder of such an operation must be in the interval
[0, 𝑚) where 𝑚 is the divisor (or modulus). Notice that this is a half-
open interval. For example if we divide 31 by 6, the remainder is 1. This
is implemented in Python with the modulo operator, %. See also: modulo,
and relevant sections in Chapter 4.
replacement field
Within an f-string, a replacement field indicates where expressions are
to be interpolated within the string. Replacement fields are delimited by
curly braces. Example: f"Hello {name}, it's nice to meet you!"
representation error
Representation error of floating-point numbers is the inevitable result
of the fact that the real numbers are infinite and the representation of
numbers in a computer is finite—there are infinitely more real numbers
than can be represented on a computer using a finite number of bits.
return value
All Python functions return a value, and we call this the return value. For
example, math.sqrt() returns the square root of the argument supplied
(with some restrictions). As noted, all Python functions return a value,
though in some cases the value returned is None. Functions which return
None include (but are not limited to) print() and certain list methods
which modify a list in place (e.g., .append(), .sort()).
356 Glossary
rubberducking
Rubberducking is a process whereby a programmer tries to explain their
code to a rubber duck, and in so doing (hopefully) solves a problem or
realizes what needs to be done in order to fix a bug. Rubber ducks are
particularly useful in this respect in that they listen without interruption,
and, not being too bright, they require the simplest possible explanation
from the programmer. If you get stuck, talk to the duck!
run time
Run time (or sometimes runtime) refers to the time at which a program
is run.
scope
Scope refers to the visibility or lifetime of a name (or identifier). For ex-
ample, variables first defined in assignment statements within a function
or formal parameters of a function are local to that function, and when
the function returns and its namespace is destroyed such local variables
are out of scope.
We often refer to inner scope as the scope within the body of a function
or method, and outer scope to the code outside the body of a function.
See also: Chapter 5 Functions, and glossary entry for namespace.
script mode
Script mode refers to the mode of operation at work when we run a
program that we’d previously written and saved. This is distinct from
interactive mode which takes place in the Python shell.
seed
A seed is a starting point for calculations used to generate a pseudo-
random number (or sequence of pseudo-random numbers). Usually, when
using functions from the random module, we allow the random number
generator to use the seed provided by the computer’s operating system
(which is designed to be as unpredictable as possible). Sometimes, how-
ever, and especially in cases where we wish to test code which includes
the use of pseudo-random numbers, we explicitly set the seed to a known
value. In doing so, we can reproduce the sequence of pseudo-random num-
bers. See: Chapter 12 Randomness, games, and simulations.
semantics
Semantics refers to the meaning of our code as distinct from the syn-
tax required by the language. Bugs are defects of semantics—our code
doesn’t mean (or do) what we intend it to mean (or do).
357
sequence unpacking
Sequence unpacking is a language feature which allows us to unpack val-
ues within a sequence to individual variables. Examples:
shadowing
Shadowing occurs when we use the same identifier in two different scopes,
with the name in the inner scope shadowing the name in the outer scope.
In the case of functions, which have their own namespace, shadowing
is permitted (it’s syntactically legal) and Python is not confused about
identifiers. However, even experienced programmers are often confused by
shadowing. It not only affects the readability of the code, but it can also
lead to subtle defects that can be hard to pin down and fix. Accordingly,
shadowing is discouraged (this is noted in PEP 8).
Here’s an example:
def square(x):
x = x * x
return x
if __name__ == '__main__':
x = float(input("Enter a number and I'll square it: "))
print(square(x)
358 Glossary
One confusion I’ve seen among students arises from using the same name
x. For example, thinking that because x is assigned the result of x * x
in the body of the function, that the return value is unnecessary:
def square(x):
x = x * x
return x
if __name__ == '__main__':
x = float(input("Enter a number and I'll square it: "))
square(x)
print(x) # this prints the x here from the outer scope!
The first example, above, is correct (despite shadowing) and the program
prints the square of the number entered by the user. In the second ex-
ample, however, the program does not print the square of the number
entered by the user—instead it prints the number that was originally
entered by the user.3
side effect
A side effect is an observable behavior of a function other than simply
returning a result. Examples of side effects include printing or prompting
the user for information, or mutating a mutable object that’s been passed
to the function (which affects the object in the outer scope).
Whenever writing a function, any side effects should be included by
design and never inadvertently. Hence, pure functions are preferred to
impure functions wherever possible.
slice / slicing
Python provides a convenient notation for extracting a subset from a
sequence. This is called slicing. For example, we can extract every other
letter from a string:
>>> s = 'omphaloskepsis'
>>> s[::2]
'opaokpi'
We can extract the first five letters, or the last three letters:
>>> s[:5]
'ompha'
>>> s[-3:]
'sis'
3
Yeah, OK, if the user enters 0 or 1, the program will print the square of the
number, but as they say, even a broken clock tells the right time twice a day! That’s
not much consolation, though when the user enters 3 and expects 9 as a result.
359
snake case
Snake case is a naming convention in which all letters are lower case, and
words are separated by underscores (keeping everything down low, like a
snake slithering on the ground). Your variable names and function names
should be all lower case or snake case. Sometimes stylized as snake_case.
See: PEP 8.
standard deviation
Standard deviation is a measure of variability in a distribution, sample or
population. Standard deviation is calculated with respect to the mean.
See: Chapter 14 Data analysis and presentation, for details on how
standard deviation is calculated.
statement
A statement is Python code which does not have an evaluation, and
thus statements are distinguished from expressions. Examples include
assignment statements, branching statements, loop control statements,
and with statements.
For example, consider a simple assignment statement:
>>> x = 1
>>>
Notice that nothing is printed to the console after making the assignment.
This is because x = 1 is a statement and not an expression. Expressions
have evaluations, but statements do not.
Don’t confuse matters by thinking, for example, that the control state-
ment of a while loop has an evaluation. It does not. It is true that the
condition must be an expression, but the control statement itself does
not have an evaluation (nor do if statements, elif statements, etc.).
stride
Stride refers to the step size in the context of range objects and slices.
In both cases, the default is 1, and thus can be excluded by the syntax.
360 Glossary
For example, both x[0:10] and range(0, 10) are syntactically valid. If,
however, we wish to use a different stride, we must supply the argument
explicitly, e.g., x[0:10:2] and range(0, 10, 2).
string
A string is a sequence, an ordered collection of characters (or more pre-
cisely Unicode code points).
string interpolation
String interpolation is the substitution of values into a string containing
some form of placeholder. Python supports more than one form of string
interpolation, but most examples given in this text make use of f-strings
for string interpolation. There are some use cases which justify the use
of earlier, so-called C-style string interpretation, but f-strings have been
the preferred method for most other use cases since their introduction
in 2002 with Python 3.6.
summation
A summation (in mathematical notation, with ∑) is merely the addition
of all terms in some collection (list, tuple, etc.). Sometimes, a summation
is nothing more than adding a bunch of numbers. In such cases, Python’s
built-in sum() suffices. In other cases, it is the result of some calculation
which must be summed, say, for example, summing the squares of all
numbers in some collection. In cases like this, we implement the summa-
tion in a loop.
syntax
The syntax of a programming language is the collection of all rules which
determine what is permitted as valid code. If your code contains syntax
errors, a SyntaxError (or subclass thereof) is raised.
terminal
Most modern operating systems provide a terminal (or more strictly
speaking a terminal emulator) which provides a text-based command
line interface for issuing commands.
The details of how to open a terminal window will vary depending on
your operating system.
truth value
Almost everything (apart from keywords) in Python has a truth value
even if it is not strictly a Boolean or something that evaluates to a
Boolean. This means that programmers have considerable flexibility in
choosing conditions for flow of control (branching and loop control).
For example, we might want to perform operations on some list, but
only if the list is non-empty. Python obliges by treating a non-empty as
having a “true” truth value (we say it’s truthy) and by treating an empty
list as something “false” or “falsey.” Accordingly, we can write:
if lst:
# Now we know the list is not empty and we can
# do whatever it is we wish to do with the
# elements of the list.
truthy
In Python, many things are treated as if they evaluated to True when
used as conditions in while loops or branching statements. We call such
things truthy. This includes numerics with non-zero values, and any non-
empty sequence, and many other objects.
tuple
A tuple is an immutable sequence of objects. They are similar to lists in
that they can contain heterogeneous elements (or none at all), but they
differ from lists in that they cannot be changed once created.
See: Chapter 10 Sequences for details.
type
Python allows for many different kinds of object. We refer to these
kinds as types. We have integers (int), floating-point numbers (float),
strings (str), lists (list), tuples (tuple), dictionaries (dict), functions
(function), and many other types. An object’s type determines not just
how it is represented internally in the computer’s memory, but also what
kinds of operations can be performed on objects of various types. For ex-
ample, we can divide one number by another (provided the divisor isn’t
zero) but we cannot divide a string by a number or by another string.
type inference
Python has limited type inference, called “duck typing” (which means if
it looks like a duck, and quacks like a duck, chances are pretty good it’s
a duck). So when we write
x = 17
362 Glossary
Python knows that the identifier x has been assigned to an object of type
int (we don’t need to supply a type annotation as is required in, say, C
or Java).
However, as noted in the text, Python doesn’t care a whit about the
types of formal parameters or return values of functions. Some languages
can infer these types as well, and thus can ensure that programmers can’t
write code that calls a function with arguments of the wrong type, or
returns the wrong type from a function.
Unicode
Unicode is a widely-used standard for encoding symbols (letters, glyphs,
and others). Python has provided full Unicode support since version 3.0,
which was released in 2008. For purposes of this textbook, it should suf-
fice that you understand that Python strings can contain letters, letters
with diacritic marks (accents, umlauts, etc.), letters from different al-
phabets (from Cyrillic to Arabic to Thai), symbols from non-alphabetic
writing systems (Chinese, Japanese, hieroglyphs, Cuneiform, Cherokee,
Igbo), mathematical symbols, and a tremendous variety of bullets, ar-
rows, icons, dingbats—even emojis!
unpacking
See: sequence unpacking.
variable
Answering the question, What is a variable? can get a little thorny. I
think it’s most useful to think of a variable as a name bound to a value
forming a pair—two things, tightly connected.
Take the result of this assignment
animal = 'porcupine'
Is the variable just the name, animal? No. Is the variable just the
value, 'porcupine'? No. It really is these two things together: a name
attached to a value.
Accordingly, we can speak of a variable as having a name and a value.
What’s the name of this variable? animal.
What’s the value of this variable? 'porcupine'.
We sometimes speak of the type of a variable, and while names do not
have types in Python, values do.
vertex (graph)
As noted elsewhere, a graph consists of a set of vertices (the plural of
vertex), and a set of edges (which connect vertices).
If we were to represent a highway map with a graph, the cities and
towns would be represented by vertices, and the highways connecting
them would be the edges of the graph.
Appendix B
Mathematical notation
363
364 Mathematical notation
Introduction
This covers creation of virtual environments and installing packages for
use in your own projects. If you are using an IDE other than IDLE,
chances are that your IDE has an interface for managing packages and
virtual environments. The instructions that follow are intended more for
people who are not using an IDE other than IDLE, or who are the DIY
type, or simply those who are more interested in how Python and the
Python ecosystem work. What follows is for users of Python version 3.4
or later.
that might be needed for the package you request and will automatically
download and install them, usually with a single command.
The other tool presented here is venv. This is, perhaps, a little more
abstract and can often confuse beginners, but in most cases it’s not
too complicated. What venv does is create a virtual environment where
you can install packages using pip. First, we’ll walk through the reasons
behind virtual environments and how to create one using venv. Then
we’ll see how to activate that virtual environment, install packages using
pip, and start coding.
Of course, you can follow the directions at https://packaging.pyth
on.org/en/latest/tutorials/installing-packages/#creating-and-using-
virtual-environments and https://pip.pypa.io/en/stable/installation/,
but if you want a somewhat more gentle introduction, read on.
$ . ./cs1210/bin/activate
(cs1210) $
When you ask pip to install a module, it will fetch the necessary files
from PyPI—the public repository of Python packages—then build and
install to whatever environment is active. For example, we can use pip
to install the colorama package (colorama is a module that facilitates
displaying colored text).
First, before using pip make sure you have a virtual environment
active. Notice that in the examples that follow, this virtual environment
is called my_venv (what you call yours is up to you). Example:
(my_venv) $
At this point, the colorama package is installed and ready for use. Pretty
easy, huh?
To see what packages you have in your virtual environment you can
use pip freeze. This will report all installed packages, along with depen-
dencies and version information. You can save this information to a file
and share this with others. Then all they need to do is install using this
list with pip install -r <filename here> (requirements.txt is commonly
used).
For more information and documentation, see: https://docs.python.
org/3/installing/index.html
Appendix D
File systems
By Harry Sharman
Introduction
This appendix covers the basics of the macOS, Linux, and Windows file
systems. Though there are many similarities between each of these file
systems, particularly between Mac and Linux, there are three different
sections which follow—one for each operating system. This is a basic
overview so that you can get to the fun part, programming!
If you’re already experienced with the file system of your machine
then you probably won’t learn anything new here, but maybe you can
learn something about a different OS!
macOS, Linux and Windows each have their own, different file system,
but they are all hierarchical, and all consist of files and directories (a.k.a.
folders). In a hierarchical file system, files are organized in a tree-like
structure, with one directory at the top level, and all other files and
directories below. You can think of this as a tree drawn upside down,
with the root at the top, and the branches and leaves below. Because
your file system is a tree-like structure, there’s a unique path to each
file or directory on your computer. At the end of each path is a file (or
perhaps an empty directory). Files are like the leaves of the tree.
We will go into more detail with each operating system since they are
all slightly different. (In this document, paths, or portions thereof, are
rendered in fixed-pitch typeface.)
macOS
macOS uses Finder as its default file management application. Finder is
the graphical user interface (GUI) for managing files, folders (directories),
and applications. Finder is usually in the dock at the bottom of the screen
and looks like a half-blue, half-white smiley face. Alternatively, you can
also get to the Finder by navigating to your desktop and clicking on the
369
370 File systems
background of a file or folder. At the top of your screen in the menu bar,
click File > New Finder Window in the dropdown menu.
The root directory in macOS is located in “Macintosh HD” (your
machine’s primary storage device).1 In the Finder window on the left
hand side, you will notice there is a sidebar with various folders and
locations. If you do not see this sidebar, go to View > Show Sidebar. In
the sidebar, under Locations, select your primary storage device. Again,
this is usually labeled “Macintosh HD”. This is where you can find all
the other files, folders, and applications. By default, each user with a
login on your Mac has a separate directory for their files. To access
your files and folder, like Desktop and Downloads, you should navigate to
this directory within your primary storage device: /Users/<your account
name>, for example /Users/harry.
/Users/harry is what is called a path. Path segments are separated
by /. Each path segment (apart from perhaps the last) is a directory
(folder). So /Users/harry indicates that the directory, harry, is within
another directory /Users. A typical macOS user directory will contain
other directories (folders), such as Documents, Downloads, Desktop, etc. For
example, the directory /Users/harry/Desktop is located in /Users/harry,
and /Users/harry is located in /Users. /Users is located in the root direc-
tory, indicated by the initial slash /.
To make a folder on your desktop, you can navigate to this directory:
/Users/<your account name>/Desktop (substituting your actual account
name), and then right click (press with two fingers on a trackpad or
Apple Magic Mouse) to open the context menu. Now, select New Folder,
and give your new folder whatever name you wish, and press return on
the keyboard. Congratulations, you have made a folder in your desktop!
To delete, rename, copy, etc. you can right-click on a folder or file. To
move a file or folder to a new folder, you can click and drag it into the
desired location.
Like many other operating systems, macOS uses file extensions—
typically a dot followed by two, three, or four characters. These are used
to indicate additional information about a file (and perhaps to associate
it with a program which can open it). For example, Python programs
usually have the extension .py, text files usually have the extension .txt,
and so on. However, in macOS, file extensions of applications (and cer-
tain other files or directories) are hidden by default.
Windows
Windows uses File Explorer as its default file management application.
File Explorer serves as the graphical user interface (GUI) for manag-
ing files, folders, and applications. File Explorer is typically accessible
through the taskbar at the bottom of the screen, and you can also find it
by using the Windows Start Menu. Try pressing the Windows key, and
then navigate to File Explorer.
The primary storage device in Windows is usually labeled C:. The
root directory on this drive is C:\. This is where all the other files, fold-
ers, and applications stem from. On a Windows machine, the disc drive
1
Note that the name of the primary storage device on your machine may be
different as this is customizable by the user.
371
is represented by a letter, here C:, and segments of the path are sep-
arated by backslashes (\). To access familiar folders like Desktop and
Downloads, you should follow this directory path: C:\Users\<your account
name>. Within that folder you should find other folders, for example,
Desktop (C:\Users\<your account name>\Desktop). To create a folder on
your desktop, you can navigate to this directory: C:\Users\<your account
name>\Desktop (you’ll see this shown at the bottom of the Windows File
Explorer window), then right-click (press with two fingers on a trackpad
or right-click on a mouse) to open the context menu. Now, select New
and then choose Folder. You can name the folder whatever you wish and
press Enter on the keyboard to complete the operation. Congratulations,
you have made a folder on your desktop.
To perform various file operations like deleting, renaming, copying,
etc., you can right-click on the file or folder to bring up a context menu.
To move a file or folder to a new location, you can click and drag it into
the desired folder.
Windows File Explorer hides file extensions for known file types by
default (including files such as Python programs). However, you can
enable the display of file extensions through the View tab in the File
Explorer ribbon. File extensions tell us what type of file something is.
For example, a Python file has a .py extension, a text file has a .txt
extension, and so on.
Linux
Linux uses a file management application called File Manager or Nautilus
(in some Linux distributions) as its default graphical user interface (GUI)
for handling files, folders, and applications. File Manager or Nautilus are
usually accessible from the system menu or application launcher.
In Linux, the root directory is represented as /, and it serves as the
top-level directory from which all other files, directories, and applica-
tions branch. To access commonly used folders, such as Desktop and
Downloads, you should follow this directory path: /home/<your account
name>/. The /home directory contains user-specific directories, and <your
account name> is a placeholder for the name of your user account.
Just like in macOS, each forward slash (/) in the directory path de-
notes a new folder. For instance, the path to your desktop folder is
/home/<your account name>/Desktop. Put another way, your Desktop folder
is located in /home/<your account name>.
To create a folder on your desktop, you can navigate to this directory:
/home/<your account name>/Desktop/, then right-click to open the context
menu. Select Create New Folder, name it as desired, and press Enter on
the keyboard (details may vary by distribution). Congratulations, you
have successfully created a folder on your desktop.
For various file operations like deletion, renaming, copying, etc., you
can right-click on the file or folder. To move a file or folder to a different
location, click and drag it to the desired destination.
By default, Linux may hide file extensions for known file types. How-
ever, you can enable the display of file extensions through the file man-
ager’s settings or preferences.
Appendix E
Here is the code used to generate the tessellated pattern used on the cover
of this book. It’s written in Python, and makes use of the DrawSVG
package. It’s included here because it’s a concise example of a com-
plete Python program which includes docstrings (module and function),
imported Python modules, an imported third-party module, constants,
functions and functional decomposition, and driver code. This is all writ-
ten in conformance to PEP 8 (or as close as I could manage with the
constraint of formatting for book page).
"""
Cover background for ITPACS.
Clayton Cafiero <cbcafier@uvm.edu>
373
374 Code for cover artwork
if __name__ == '__main__':
translate_y = 0.0
for i in range(ROWS):
if i % 2: # odd rows
translate_x = 0.0
else: # even rows
translate_x = TRAPEZOID_BASE_SEGMENT + TRAPEZOID_LEG
for j in range(COLS):
colors = random.sample(COLORS, 3)
d.append(draw_cube(translate_x,
translate_y,
colors))
translate_x += TRAPEZOID_BASE + TRAPEZOID_LEG
d.set_pixel_scale(1.0)
d.save_svg('cover.svg')
d.save_png('cover.png')
Index
__name__, 157
edge, 342
empty sequence, 342
entry point, 342
escape sequence, 36, 37, 343
Index 379
identifier, 347
IDLE, 349
IEEE 754, 39
immutable, 30, 347
import, 347
incremental development, 348
index, 185, 238, 348
information hiding, 87
input validation, 224
input/output (I/O), 348
instance, 348
instantiate, 276
integrated development environment (IDE), 348
interactive mode, 2, 16, 349
interpretation, 14
interpreter, see Python interpreter
ISO 4217, 122
iterable, 229, 349
nested, 245
while, 217, 219, 221, 223, 227
Matplotlib, 351
mean, 281–283, 286, 287
Mersenne twister, 262
method, 352
module, 352
modulus, 60, 352
Monte Carlo method, 258, 352
mutable, 30, 352
names, 46
namespace, 353
naming
camel case, 100
snake case, 100
word caps, 100
natural numbers, 38
normal distribution, 283, 286
Parnas, David, 87
PEP 8, 98, 354
product, 238
proposition, 130
pseudo-random, 261, 354
pseudocode, 103
Python interpreter, 15
Python shell, 16
Python virtual machine, 337
382 Index
Python-supplied modules
csv, 338
math, 91
random, 258
statistics, 287
terminal, 360
top-level code environment, 157, 360
Index 383
while, 224
with, 340