bgpython_usl_c_2
bgpython_usl_c_2
For Beginners
1 Intro 1
1.1 Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Platform and Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Official Homepage and Books For Sale . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.4 Email Policy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.5 Mirroring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.6 Note for Translators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.7 Copyright, Distribution, and Legal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.8 Dedication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.9 Publishing Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
ii
CONTENTS iii
7 Strings 41
7.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.3 What is a String? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.4 Creating Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.5 Converting Other Types To Strings and Vice Versa . . . . . . . . . . . . . . . . . . . . 42
7.6 String Concatenation with + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.7 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.8 Getting Individual Characters From Strings . . . . . . . . . . . . . . . . . . . . . . . . . 44
7.9 Slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.10 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
7.11 Interlude: Mutable versus Immutable Types . . . . . . . . . . . . . . . . . . . . . . . 46
7.12 for-loops with Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.13 String Functions and Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
7.14 Formatted Output with F-Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
7.14.1 .format() Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.14.2 % printf Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.15 Chapter Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
7.16 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
7.17 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8 Lists 57
8.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.3 What Are Lists? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
8.4 List Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
8.5 for and Lists—Powerful Stuff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
8.6 for and enumerate() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.7 Midterm: Doubling The Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.8 Built-in Functions for Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
8.9 What Good Are They? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
8.10 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
8.11 Building New Lists, Repeating and Empty . . . . . . . . . . . . . . . . . . . . . . . . 66
iv CONTENTS
9 Dictionaries 81
9.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
9.3 What are Dictionaries? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
9.4 Initializing a Dictionary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.5 Speed Demon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.6 Does this dict have this key? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
9.7 Iterating over Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
9.8 Common Built-in Dictionary Functionality . . . . . . . . . . . . . . . . . . . . . . . . 85
9.9 Dictionary Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.10 Dictionaries of Dictionaries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
9.11 Dictionaries are Mutable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.12 The Chapter Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
9.13 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
9.14 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
10 Functions 95
10.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
10.3 What Are Functions? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
10.4 Using Built-In Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96
10.5 Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
10.6 Writing Your Own Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
10.7 Multiple Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
10.8 What Makes a Good Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
10.9 Positional Arguments versus Keyword Arguments . . . . . . . . . . . . . . . . . . . . . 101
10.10 Interlude: Evaluation Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
10.11 The Chapter Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
10.12 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109
10.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
14 Exceptions 167
14.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
14.2 Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
14.3 Errors in Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
14.4 Classic Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
14.5 Error Handling with Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
14.6 Catching Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
14.7 Catching Multiple Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170
14.8 Catching Multiple Exceptions II . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
14.9 Getting More Exception Information . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
14.10 Catching All Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
14.11 Finally finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
14.12 What Else? else! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
14.13 Exception Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
14.14 Raising Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
14.15 Re-raising Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
14.16 Project Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177
14.17 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181
14.18 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182
Intro
This is an alpha-quality book. There are mistakes, oh yes. When you find them, please drop an issue in
GitHub, or a pull request, or email me at [email protected]. When the number of defects gets low enough,
I’ll offer a print version.
Hey, everyone! Have you been thinking about learning to program? Have you also been thinking of how to
do it in the easy-to-approach Python programming language?
Yes? Then this is the book for you. We’re going to start with the absolute basics and build up from there,
building up to being an intermediate developer and problem-solver! Python is the language we’ll be using
to make this happen.
But by the end of the book, you will have developed programming techniques that transcend languages.
After picking up Python, maybe try another language like JavaScript, Go, or Rust. They all have their own
features to explore and learn.
1.1 Audience
Beginning programmers. If you have limited experience or no experience, this book is targeted at you!
Attitude Prerequisite: be inquisitive, curious, have an eye for puzzles and problem solving, and be willing to
take on difficult challenges.
Technical Prerequisite: be a computer user. You know what files are, how to move them and delete them,
what subdirectories (folders) are, can install software, and how to type.
Are you a seasoned developer looking to start with Python? I’m sorry but this is not likely to be the book
you’re looking for. It will progress too slowly for your tastes. Just jump straight into the official Python
documentation1 .
1
2 Chapter 1. Intro
1.5 Mirroring
You are more than welcome to mirror this site, whether publicly or privately. If you publicly mirror the site
and want me to link to it from the main page, drop me a line at [email protected].
or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.
2
http://www.catb.org/~esr/faqs/smart-questions.html
1.8. Dedication 3
One specific exception to the “No Derivative Works” portion of the license is as follows: this guide may
be freely translated into any language, provided the translation is accurate, and the guide is reprinted in its
entirety. The same license restrictions apply to the translation as to the original guide. The translation may
also include the name and contact information for the translator.
The C source code presented in this document is hereby granted to the public domain, and is completely free
of any license restriction.
Educators are freely encouraged to recommend or supply copies of this guide to their students.
Unless otherwise mutually agreed by the parties in writing, the author offers the work as-is and makes no
representations or warranties of any kind concerning the work, express, implied, statutory or otherwise, in-
cluding, without limitation, warranties of title, merchantability, fitness for a particular purpose, noninfringe-
ment, or the absence of latent or other defects, accuracy, or the presence of absence of errors, whether or not
discoverable.
Except to the extent required by applicable law, in no event will the author be liable to you on any legal
theory for any special, incidental, consequential, punitive or exemplary damages arising out of the use of the
work, even if the author has been advised of the possibility of such damages.
Contact [email protected] for more information.
1.8 Dedication
Thanks to everyone who has helped in the past and future with me getting this guide written. And thank
you to all the people who produce the Free software and packages that I use to make the Guide: GNU,
Linux, Slackware, vim, Python, Inkscape, pandoc, many others. And finally a big thank-you to the literally
thousands of you who have written in with suggestions for improvements and words of encouragement.
I dedicate this guide to some of my biggest heroes and inspirators in the world of computers: Donald Knuth,
Bruce Schneier, W. Richard Stevens, and The Woz, my Readership, and the entire Free and Open Source
Software Community.
2.1 Objectives
• Be able to explain what a programmer does
• Be able to explain what a program is
• Be able to summarize the four big steps in solving problems
5
6 Chapter 2. What is Programming, Anyway?
When you’re first starting, large programs like that seem impossible. But here’s the secret: each of those
large programs is made up of smaller, well-designed building blocks. And each of those building blocks is
made up of smaller of the same.
And when you start learning to be a programmer, you start with the smallest, most basic blocks, and you
build up from there.
And you keep building! Writing software is a lifelong learning process. There are always new things to
learn, new technologies, new languages, new techniques. It’s a craft to be developed and perfected over a
lifetime. Sure, at first you won’t have that many tools in your toolkit. But every moment you spend working
on software gives you more experience solving problems and gives you more methods to attack them.
One of my favorite problem-solving frameworks was popularized by mathematician George Pólya in 1945 in
his book How To Solve It. It was originally written for solving math problems, but it’s surprisingly effective
at solving just about anything. The Four Main Steps are:
1. Understanding the Problem. Get clarity on all parts of the problem. Break it down into subproblems,
and break those subproblems down. If you don’t understand the problem, any solutions you come up
with will be solving the wrong problem! You know you understand the problem when you can explain
it to someone completely.
2. Devising a Plan. How are you going to attack this with the tools you have at your disposal and the
techniques you know? You know you’re done making a plan when you’re able to easily convert your
plan into code.
Often when planning you realize there’s something about the problem you don’t fully understand. Just
for a bit, pop back to Step 1 until it’s clear, then come back to planning.
3. Carrying out the plan Convert your plan into code and get it working.
Often in this phase, you find that there was either something you didn’t understand or something the
plan didn’t account for. Drop back a step or two until it’s resolved, then come back here.
4. Looking Back. Look back on the code you got working, and consider what went right and what
went wrong. What would you do differently next time? What techniques did you learn while writing
the code? Was there any place you could have structured things better, or anyplace you could have
removed redundant code?
What’s neat about this is that developers apply the steps of problem-solving to the entire program, and they
also apply it to the smaller problems within the program. A big computing problem is always composed of
many subproblems! The problem-solving framework is used within the problem-solving framework!
An example of a real-life problem might be “build a house”. But that’s made up of subproblems, like “build
a foundation” and “frame the walls” and “add a roof”. And those are made up of subproblems, like “grade
the lot” and “pour concrete”.
In programming, we break down problems into smaller and smaller subproblems until we know how to solve
them with the techniques we know. And if we don’t know a technique to solve it, we go and learn one!
Being a developer is the same as being a problem solver. The problems ain’t easy, but that’s why it pays the
big bucks.
So you should expect that any time you see a programming problem in this book, on a programming challenge
website, at school, or work, that the answer will not be obvious. You’re going to have to work hard and spend
a lot of time to get through the first problem-solving steps before you’ll even be ready to start coding.
2.6 Summary
• A programmer is a problem solver. They then write programs that implement a solution to that problem.
• A program is a series of instructions that can be carried out by a computer to solve the problem.
• The main problem-solving steps are: Understand the Problem, Devise a Plan, Carry out the Plan, Look
Back.
Chapter 3
3.1 Objectives
• Install Python, and explain what it does
• Learn what an Integrated Development Environment (IDE) is.
9
10 Chapter 3. What software will I need?
3.3.2 Mac
Download and install Python for Mac from the official website2 .
Another option to installing Python on Mac is through Homebrew. We’ll cover this later.
3.3.3 Linux/Unix-likes
The Linux community tends to be pretty supportive of people looking to install things. Google for something
like ubuntu install python3, replacing ubuntu with the name of your distribution.
Platform Commands
Windows Hit the Start menu and type “idle”. It should show up in the pick list and you can click to
open it.
Mac Hit CMD-SPACE and type “idle”. It should show up in the pick list and you can click to
open it.
Unix-like Type idle in the terminal or find it in your desktop pulldown menu.
If you run idle on the command line and it says something about the command not being found, try running
idle3.
If you get an error on the command line that looks like this:
** IDLE can't import Tkinter.
Your Python may not be configured for Tk. **
you’ll have to install the Tk graphical toolkit. This might be a package called tk or maybe python-tk. If
you’re on a Unix-like, search for how to install on your system. On a Mac with Homebrew, you can brew
install python-tk.
If you get another error, cut and paste that error into your favorite search engine to see what other people say
about how to solve it.
Once IDLE is up, you should see a window that looks vaguely like this:
1
https://www.python.org/downloads/
2
https://www.python.org/downloads/
3.5. Your First Command 11
and hit RETURN. This commands Python to output the words “Hello, world!”.
>>> print("Hello, world!")
Hello, world!
And it did!
This is just the beginning!
3.6 Summary
• The integrated development environment (IDE) has an editor, a debugger, and a terminal window.
• The code editor in the IDE is where you’ll be typing your programs.
• The programs, also known as code, are a series of instructions that Python will execute.
• Python is a program that will run your Python programs!
12 Chapter 3. What software will I need?
Chapter 4
4.1 Objectives
• Edit some source code in the IDLE editor.
• Run that program.
13
14 Chapter 4. How do I write a program?
The name is split into two parts, commonly, separated by a period. The first part is the name, and
the second part is the extension. Confusingly sometimes people refer to the name and extension
together as the “name”, so you’ll have to rely on context.
{.default} hello.py
There we have a file named hello and an extension .py. This is a common extension that means
“this is a Python source code file”.
Type the following1 into the editor (the line numbers, below, are for reference only and shouldn’t be typed
in):
1 print("Hello, world!")
2 print("My name's Beej and this is (possibly) my first program!")
If you haven’t saved the file, it will prompt you to save the file. (You can pull down “File→Save”, or hit
COMMAND-S or CTRL-S to do this preemptively.)
And then, in the console window, you’ll see the output appear! [Angelic Chorus!]
Hello, world!
My name's Beej and this is (possibly) my first program!
Did you miss it? Hit F5 again and you’ll see it appear again.
You just wrote some instructions and the computer carried it out!
Okay, so maybe there might be a small number of in between things that I skimmed over, but, as Obi-Wan
Kenobi once said, “You’ve taken your first step into a larger world.”
4.5 Exercises
Remember to use the four problem-solving steps to solve these problems: understand the problem, devise a
plan, carry it out, look back to see what you could have done better.
1. Make another program called dijkstra.py that prints out your three favorite Edsger Dijkstra quotes2 .
1
https://beej.us/guide/bgpython/source/examples/hello.py
2
https://en.wikiquote.org/wiki/Edsger_W._Dijkstra
4.6. Summary 15
4.6 Summary
• Use the problem solving framework!
• Edit some source code in the IDLE editor.
• Run that program from within IDLE.
16 Chapter 4. How do I write a program?
Chapter 5
5.1 Objective
• Understand what data is and how it is used
• Understand what variables are and how they are used
• Utilize variables to store information
• Print the value of variables on the screen
• Do basic math
• Store input from the keyboard in variables
• Learn about integer versus string data types
• Convert between data types
• Write a program that inputs two values and prints the sum
For this chapter, we want to write a program that reads two numbers from the keyboard and prints
out the sum of the two numbers.
17
18 Chapter 5. Data and Processing Data
Enter the following code in a new program in your editor, save it, and give it a run. (This is just like you did
with the program in the earlier chapter. You can name this one anything you’d like. If you need inspiration,
vartest.py1 seems good to me.)
1 x = 34 # Variable x is assigned value 34
2 print(x)
3 x = 90 # Variable x is assigned value 90
4 print(x)
In Python, variables refer to values2 . We’re saying on line 1 of the code, above, “The variable x refers to
the value 34.” Another way to think of this that might be more congruent with other languages is that x is a
bucket that you can put a value in.
Then Python moves to the next line of code and runs it, printing 34 to the screen. And then on line 3, we
put something different in that bucket. We store 90 in x. The 34 is gone–this type of bucket only holds one
thing3 .
So the output will be:
34
90
You can see how the variable x can be used and reused to store different values.
We’re using x and y for variable names, but they can be made up of any letter or group of letters,
or digits, and can also include underscores (_). The only rule is they can’t start with a digit!
These are all valid variable names (and, of course, you can make up any name you want!):
y
a1b2
foo
Bar
FOOBAZ12
Goats_Rock
You can also do basic math on numeric variables. Add to the code above:
1 x = 34 # Variable x is assigned value 34
2 print(x)
3 x = 90 # Variable x is assigned value 90
4 print(x)
5
On line 6, we introduced a new variable, y, and gave it the value of “whatever x’s value is plus 40”.
What are all these # marks in the file? We call those hash marks, and in the case of Python, they
mean the rest of the line is a comment and will be ignored by the Python interpreter.
One last important point about variables: when you do an assignment like we did, above, on line 6:
y = x + 40 # y is assigned 130
When you do this, y refers to the value 130 even if x changes later. The assignment happens once, when that
line is executed, with the value in x at that snapshot in time, and that’s it.
1
https://beej.us/guide/bgpython/source/examples/vartest.py
2
More generally speaking, variables refer to objects, but since all we have for now is numeric values, let’s just go with values.
3
Later we’ll learn that other types of buckets can hold more than one thing.
5.2. Data, Variables, and Math 19
10 x = 1000
11 print(y) # Still 130!
Even though we had y = x + 40 higher up in the code, x was 90 at the time that code executed, and y is set
to 130 until we assign into it again. Changing x to 1000 did not magically change y to 1040.
Fun Tax Fact: The 10404 is nearly my least-favorite tax form.
For more math fun, you have the following operators at your disposal (there are more, but this is enough to
start):
Function Operator
Add +
Subtract -
Multiply *
Divide /
Integer Divide5 //
Exponent **
You can also use parentheses similar to how you do in algebra to force part of an expression to evaluate first.
Normal mathematical order of operations rules apply6 .
8 + 4 / 2 # 8 + 4 / 2 == 8 + 2 == 10
(8 + 4) / 2 # (8 + 4) / 2 == 12 / 2 == 6
x = x + 5 # x = 10 + 5 = 15
This pattern is so common, there’s a piece of shorthand7 that we can use instead.
These two lines are identical:
x = x + 10
x += 10
As are these:
4
https://en.wikipedia.org/wiki/Form_1040
5
Integer division truncates the part of the number after the decimal point.
6
https://en.wikipedia.org/wiki/Order_of_operations
7
We call shorthand like this syntactic sugar because it makes things that much sweeter for the developers.
20 Chapter 5. Data and Processing Data
x = x / 5
x /= 10
These are very frequently used by devs. If you have x = x + 2, use x += 2, instead!
Something interesting happens here that I want you to make note of. It’s not going to be super useful right
now, but it will be later when we get to more intermediate types of data.
When you do this, both x and y refer to the same 1000.
That’s a weird sentence.
But think of it this way. Somewhere in the computer memory is the value 1000. And both x and y refer to
that single value.
If you do this:
x = 1000
y = 1000
Now there are two 1000 values. x points to one, and y points to the other.
Finally, adding on to the original example:
x = 1000
y = x
y = 1000
What happens there is that first there is one 1000, and x refers to it.
Then we assign x into y, and now both x and y refer to the same 1000.
But then we assign a different 1000 to y, so now there are two 1000s, referred to by x and y, respectively.
(The details of this are rather more nuanced than I’ve mentioned here. See Appendix C if you’re crazy
enough.)
Takeaway: variables are just names for items in memory. You can assign from one variable to another and
have them both point to the same item.
We’re just putting that in your brain early so that we can revive it later.
5.4. Your Mental Model of Computation 21
When you’re programming, it’s important to keep a mental model of what should happen when this program
runs.
Let’s take our example from before. Step through it in your head, one line at a time. Keep track of the state
of the system as you go:
1 x = 34 # Variable x is assigned value 34
2 print(x)
3 x = 90 # Variable x is assigned value 90
4 print(x)
Before we begin, x has no value. So represent that in your head as “x has no value; it’s invalid”.
x is now 34.
Then we’re out of code, so the program exits. And we have “34” and “90” on the screen from when they
were printed.
This is the key to being able to debug. When your mental computing model shows different results than the
actual program run, you have a bug somewhere. You have to dig through your code to find the first place
your mental model and the actual program run diverge. That’s where your bug is.
We want to get input from the user and store it in a variable so that we can do things with it.
Remember that our goal in this chapter is to write a program that inputs two values from the user on the
keyboard and prints the sum of those values.
Python comes with a built-in function that allows us to get user input. It’s called, not coincidentally, input().
A function is a chunk of code that does something for you when you call it (that is when you ask it to).
Functions accept arguments that you can pass in to cause the function to modify its behavior. Additionally,
functions return a value that you can get back from the function after it’s done its work.
22 Chapter 5. Data and Processing Data
So here we have the input() function8 , which reads from the keyboard when you call it. As an argument,
you can pass in a prompt to show the user when they are ready to type something in. And it returns whatever
the user typed in.
What do we do with that return value? We can store it in a variable! Let’s try!
Here’s another program, inputtest.py9 :
1 # Take whatever `input()` returns and store it in `value`:
2
Check it out! We entered the value 3490, stored it in the variable value, and then printed it back out! We’re
getting there!
But you can also call it like this:
$ python3 inputtest.py
Enter a value: Goats rock!
You entered Goats rock!
Hmmm. That’s not a number. But it worked anyway! So are we all good to go?
Yes… and no. We’re about to discover something very important about data.
And, no, it’s not a number, indeed. It’s a sequence of characters, which we call a string. A string is something
like a word, or a sentence, for example.
Wait… there’s another type of data besides numbers? Yes! Lots of types of data! We call them data types.
Python associates a type with every variable. This means it keeps track of whether a variable holds an integer,
a floating point10 number or a string of characters.
Here are some examples and their associated types. When you store one of these values in a variable, the
variable remembers the type of data stored within it.
8
input() is what we call a built-in in Python. It comes with the language and we get to make use of it. Later we’ll learn to write
our own functions from scratch!
9
https://beej.us/guide/bgpython/source/examples/inputtest.py
10
This is the way most computers represent numbers with a decimal point in them, such as 3.14159265358979. When you see
“floating point” or “float”, think “number with a decimal point in it” as opposed to “integer”.
5.7. Converting Between Data Types 23
In the examples, above, strings are declared using double quotes ("), but they can also be done with single
quotes, as long as the quotes match on both ends:
"Hello!" # is the same as 'Hello!'
'Hello!' # is the same as "Hello!"
Okay, that’s all fine. But is input() returning a string or a number? We saw both happen when we tried it
out, right?
Actually, turns out, input() always returns a string. Period. Even if that’s a string of numbers. Note that
these things are not the same:
3490 # int, a numeric value we can do math with
"3490" # string, a sequence of characters
Sure, they look kinda the same, but they aren’t the same because they have different types. You can do
arithmetic on an int, but not on a string.
Well, that’s just great. The task for this chapter is to get two numbers from the keyboard and add them
together, but the input() function only returns strings, and we can’t add strings together numerically!
How can we solve this problem?
print(b + 5) # 3495
How did that work? We called the built-in int() function and passed it a string "3490". int() did all the
hard work and converted that string to an integer and returned it. We then stored the returned value in y. And
finally, we printed the value of b+5 just to show that we could do math on it.
Perfect!
Here are some of the conversion functions available to you in Python:
24 Chapter 5. Data and Processing Data
Function Effect
int() Convert argument to an integer and return it
float() Convert argument to a floating-point number and return it
str() Convert argument to a string and return it
So… given all that we know so far, how can we solve this chapter’s problem: input two numbers from the
keyboard and print the sum?
If we’re satisfied that our plan is solid, it’s time to move to the next phase.
Problem-solving step: Carrying out the Plan.
Now let’s convert each of those lines to real Python. I’ll throw in the pseudocode as comments so we can
see how they compare. (Source code link11 .)
1 # Read string from keyboard into variable x
2 x = input("Enter a number: ")
3
11
https://beej.us/guide/bgpython/source/examples/twosum.py
5.9. Wrapping it Up 25
5.9 Wrapping it Up
Problem-solving step: Looking Back.
This grimly-named step is where we take a look at our code and decide if there was a better way to attack
this problem. It’s important to remember that coding is a creative endeavor. There are many ways to solve
the same problem.
Admittedly, right now, you don’t have many tools in the toolkit, so your creativity is limited. But eventually,
in the not-too-distant future, you’ll know several ways to solve a problem, and you’ll have to weigh the pros
and cons of each, and be creative and choose one!
26 Chapter 5. Data and Processing Data
5.10 Exercises
“You know how to get to Carnegie Hall?”
“Practice!”
Zeus says, “This book assumes you complete all of the exercises!” and when Zeus speaks, people really
should listen.
I know, I know. You get to the exercises part of a book and you just skip ahead. I mean, it’s not like I’m
grading you or anything.
But there’s only one way to get to be a better dev: practice and repetition. Going through this book without
doing the exercises is like training for a marathon by reading about how to run. It’s simply not going to get
you there on its own.
Resist the urge to look at the solution until you’ve solved it! Give yourself a time limit. “If I haven’t
solved this in 20 minutes, I can look at the solution.” That 20 minute isn’t wasted—it’s invaluable problem-
solving practice time. During that time, you’re building a scaffold in your brain that can hold the solution
once you see it.
If you just skip straight to the solution, look at it, and say, “Yup, makes sense, I got it,” you’re missing out
on all that benefit.
Don’t shortchange yourself! Do the exercises! The more you do, the better dev you’ll be! I’m getting off
my soapbox now!
Remember to use the four problem-solving steps to solve these problems: understand the problem, devise a
plan, carry it out, look back to see what you could have done better.
Here they are:
1. Make a version of the two number sum code that works with floats instead of ints. Do the numbers
always add correctly, or are they sometimes just a little bit off? Lesson: floating point math isn’t always
exact. Sometimes it’s off by a tiny fraction. (Solution12 .)
2. Have the program print out the sum and the difference between two numbers. (Solution13 .)
3. Allow the user to enter 3 numbers and perform the math on those. (Solution14 .)
4. Write a program that allows the user to enter a value for 𝑥, and then computes and prints 𝑥2 . Remember
** is the exponentiation operator in Python. 3**2 is 9. (Solution15 .)
5. Write a program that allows the user to enter a, b, and c, and the solves the quadratic formula16 for
those values.
A refresher: with equations of the form:
𝑎𝑥2 + 𝑏𝑥 + 𝑐 = 0
12
https://beej.us/guide/bgpython/source/examples/ex_twosumfloat.py
13
https://beej.us/guide/bgpython/source/examples/ex_twosumdiff.py
14
https://beej.us/guide/bgpython/source/examples/ex_threesumdiff.py
15
https://beej.us/guide/bgpython/source/examples/ex_xsquared.py
16
https://en.wikipedia.org/wiki/Quadratic_formula
5.10. Exercises 27
What is that ± symbol after −𝑏 in the equation? That’s “plus or minus”. It means there are actually
two equations, one with + and one with −:
√ √
−𝑏+ 𝑏2 − 4𝑎𝑐 𝑏2 − 4𝑎𝑐
−𝑏−
𝑥𝑝𝑙𝑢𝑠 = 𝑥𝑚𝑖𝑛𝑢𝑠 =
2𝑎 2𝑎
Solve them both and print out both answers for a given 𝑎, 𝑏, and 𝑐.
What about that square root of 𝑏2 − 4𝑎𝑐? How do you compute that? Here’s a demo program for
computing the square root of 2—use it to learn how to use the math.sqrt() function, and then apply
it to this problem.
1 import math # You need this for access to the sqrt() function
2
Code that up and, hey! You’ve written a program that solves quadratic equations! Take that, home-
work! (Solution17 .)
6. Followup to the previous question: after computing x, go ahead and compute the value of
𝑎𝑥2 + 𝑏𝑥 + 𝑐
and print it out. (You can use either the plus solution or the minus solution—doesn’t matter since
they’re both solutions.) The result should be exactly 0. Is it? Or is it just something really close to
zero? Lesson: floating point math isn’t always exact. Sometimes it’s off by a tiny fraction.
Sometimes you might get a number back that looks like this, with a funky e-16 at the end (or e-
something):
8.881784197001252e-16
That’s a floating point number, but in scientific notation18 . That e-16 is the same as ×10−16 . So the
math equivalent is:
8.881784197001252 × 10−16
Now, 10−16 is actually a really, really small number. So if you see something like e-15 or e-18 at the
end of a float, think “that’s a really small number, like close to zero.”
17
https://beej.us/guide/bgpython/source/examples/ex_quadratic.py
18
https://en.wikipedia.org/wiki/Scientific_notation
28 Chapter 5. Data and Processing Data
(Solution19 .)
7. Make up two more exercises and code them up.
And don’t worry–we’ll get away from the math examples soon enough. It’s just, for now, that’s about all we
know. More soon!
5.11 Summary
This chapter we covered:
• Data and variables
• Storing and printing data in variables
• Doing basic math
• Getting keyboard input
• Data types, and conversions between them
– String
– Integer
– Floating Point
• Keeping the problem-solving framework in mind the whole time!
It’s a great start, but there’s plenty more to come!
19
https://beej.us/guide/bgpython/source/examples/ex_quadratic2.py
Chapter 6
6.1 Objective
• Understand what flow control is
• Understand what a conditional is
• Be able to construct Boolean (“BOO-lee-in”) expressions
• Implement code that makes decisions with if statements
• Implement code that loops with a while loop
• Implement code that loops with a for loop and range iterator
When this program runs, Python keeps track of the current instruction, or line, if you will.
29
30 Chapter 6. Flow Control and Looping
All right! Let’s do a variant on AND, namely OR. With OR, the entire expression is true if either or both of
the subexpressions is true.
For example, I don’t like running, but I do like bicycling. Nevertheless, the following statement is true,
because at least one of the subexpressions is true: “I like running OR I like bicycling”. True.
This is the basis for the smarty-pants answer to the question:
“Would you like soup or salad?”
“True. I would like soup or I would like salad.”
“Get out of my restaurant, Boolean fanatic!”
Level: Intermediate. Are these expressions true or false for you?
• I live in Europe OR I’m older than 25.
• It’s raining right now OR it’s sunny right now.
• Cats are superior to dogs OR dogs are superior to cats.
All right! Now one more thing to remember: unless there are parentheses in an expression saying otherwise,
AND takes precedence over OR. That is, do the ANDs first, and then do the ORs.
Level: Advanced.
Let’s say it’s raining, I’m over 25, and this fish is big. We could evaluate this expression:
It’s sunny AND this fish is big OR I’m over 25.
We do the AND first. It’s not sunny, and the fish is big. So that’s “false AND true”, which evaluates to
“false”.
So replace that AND expression with “false”. And then we’ll do the OR:
false OR I’m over 25.
Now I am over 25, so that evaluates to “false OR true”, which is “true”.
So the entire expression is true.
And you can override with parentheses:
It’s sunny AND (this fish is big OR I’m over 25).
Do the work in parens first. So now we evaluate the OR, which evaluates to “true OR true” which is “true”.
Then we evaluate the AND, which is “It’s sunny AND true”, which is “false AND true”, which is “false”.
So the entire expression is false.
Let’s do some examples with numeric conditional expressions. Do these evaluate to true or false?
• 1<5
• 5>1
• 1 < 5 AND 5 < 10
• 1 > 5 OR 5 < 10
• 1 < 5 AND 5 > 10 OR 10 > 20
• 1 < 5 AND (5 > 10 OR 10 > 20)
Answers:
• True
• True
• True AND True = True
• False OR True = True
• True AND False OR False = False OR False = False
• True AND (False OR False) = True AND False = False
32 Chapter 6. Flow Control and Looping
Sometimes developers (but more usually hardware folks) describe these operations in what are called truth
tables. A truth table shows what the result of a Boolean expression will be for some given inputs.
Often these tables use 1 to represent True and 0 to represent False2 .
Here are some truth tables for the operations we’ve seen so far.
A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1
A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1
A NOT A
0 1
1 0
Now we’re about ready to go. Let’s learn how to do this in Python.
Operator Effect
< Less than, e.g. x < y
> Greater than, e.g. x > y
== Equal to, e.g. x == y
!= Not equal to, e.g. x != y
<= Less than or equal to, e.g. x <= y
>= Greater than or equal to, e.g. x >= y
So we can take a variable and convert it to a true or false value by comparing it to numbers or other variables.
What is true and false in Python?
2
Ooooo! 1s and 0s! Binary! For just a moment, here, we’re getting a glimpse of the deep workings of the machine.
6.6. The Almighty if Statement 33
Easy enough.
Let’s try a quick demo3 :
1 print(True) # True
2 print(False) # False
3
4 x = 10
5 print(x == 10) # True
6 print(x < 5) # False
7
Check that out! You can store the Boolean result of a comparison in a variable, like we did with r, above!
It’s important to note that True and False are not strings. They represent Boolean values.
So now, for data types, we know about strings, ints, floats, and Booleans (sometimes called bools for short).
Add that to the collection of tools we have at our disposal.
But what about our good friends AND and OR?
Pretty easy, but I threw in a NOT! What is that? It’s pretty easy: it just inverts whatever you give it. “NOT
true” is false. And “NOT false” is true.
print(not False) # Prints True
Let’s take that knowledge and turn it into a complete program4 using if, and then we can take it apart in
more detail:
1 x = input("Enter a number: ")
2 x = float(x)
3
So if x >=50 and x <= 59 is True, then we execute the block that is indented afterward.
Blocks can be indented with any combination of tabs or spaces, as long as each line in the block
begins with the same pattern of tabs or spaces. The official recommendation is to use 4 spaces
for indentation in Python.
Indented blocks in Python are one of the things most devs are pretty opinionated about in terms
of loving or hating. Personally, I say be a good dev in any language, regardless of how you feel
about their idiosyncrasies.
And then what’s this pesky else? That’s a super-handy feature of if. If the condition is False, then the
block under the else is evaluated instead. Basically, we’re saying, “If the condition is true, then do this,
otherwise do this.”
There’s one more construct we can use in the if-else family: elif. This is short for else if and is used
if you need to check multiple conditions.
1 if x < 10:
2 print("x is less than 10")
3 elif x < 20:
4 print("x is less than 20")
5 elif x < 30:
6 print("x is less than 30")
7 else:
8 print("x is greater than or equal to 30")
In that example, first we check if x is less than 10. If that’s False, the next condition is tested, and so on. If
none of them match, then we get to the else case.
The if statement is the core of what allows us to use Boolean logic to control the flow of our program. It’s
how computers can make decisions based on input. Without if, there would be no computing–that’s how
important it is!
And your job as a dev is to come up with that logic, those if statements and conditions, that cause your
program to give the proper output for a given input.
Here’s a real-life example. Let’s say you have to add some shingles to a roof. The steps to do so are to place
a shingle, nail it in place, and then move to the next spot.
• Place a shingle
• Nail it on
• Move to the next spot
• Place a shingle
• Nail it on
• Move to the next spot
• Place a shingle
• Nail it on
• Move to the next spot
• Place a shingle
• Nail it on
• Move to the next spot
That’s us looping. We’re running the same piece of code while a condition is True. At the very least, this
can save us a lot of typing!
Python has several looping statements, but for this section, we’ll concentrate on what’s called the while-loop.
It does something while a condition is true.
7 print("All done!")
It repeats the body of the loop (everything that’s indented) as long as the condition x <= 1210 is True.
You see inside the body of the loop, we increment (add one to) x every iteration so that it increases toward
1210.
What would happen if we didn’t increment x? In that case, it would loop forever. We call this an infinite
loop. If your program’s running for a long time with no output (or repeating output), it might be in an infinite
loop.
How do you get out of your program if it’s caught in an infinite loop? You hit CTRL-C (AKA “break”).
That’ll get you back to your shell prompt.
Remember one of our goals for this chapter’s program is to ask the user for a number between 5 and 50. And
we need to ask them again if they enter a number outside that range. That is, we need to loop while the user
has not given us valid input. Give that some thought now, and we’ll come back to it later.
5
https://beej.us/guide/bgpython/source/examples/while.py
36 Chapter 6. Flow Control and Looping
Now how much would you pay? It slices, it dices! But we’re not done yet! You can also tell range() how
far to skip each step!
Let’s print out only the even numbers between 4 and 18 (that is, print from 4 to 18, stepping by 2 each time):
for i in range(4, 20, 2): # loop from 4 to 18, skipping by 2 each time
print(i)
Question: let’s say I wanted to count down from 10 to 1 using range(). How would I do that?
for i in range(???, ???, ???):
print(i)
If you want to loop a number of times that you know in advance, like 10 times, or the number stored in x
times, then use a for loop with range().
If you just want to loop until some condition is True or False, but you don’t know when that’ll be, use a
while loop.
Don’t look now, but our “plan” is looking like really good pseudocode at this point.
Let’s go ahead and code up the user input portion. We’ll do printing asterisks later.
Problem-solving step: Carrying out the Plan.
Asking the user for input, we already know.
But how do we ask them repeatedly until they enter something valid? We need to loop! How about looping
while the input is invalid? Sure!
1 input_valid = False # Assume it's invalid to start
2
3 while not input_valid: # While not input valid ("while input invalid")
4 x = input("Enter a number, 5-50 inclusive: ")
5 x = int(x)
6
If you haven’t already, code that up and run it. No, it’s not the complete program, but it’s the complete first
step of the program, and we can test it before moving on just to be confident that this part works.
Run it and try it with some numbers. If you enter an invalid number, it should tell you so and ask again. If
you enter a valid number, input_valid becomes True and the while loop exits (because the continuation
condition is not input_valid).
Once you’re satisfied it’s working correctly, let’s move back to the spec and concentrate on printing out the
asterisks.
Problem-solving step: Devising a Plan.
If the user enters x, we want to print out x count of characters, total. The first 30 of these will be #, and any
after that will be *.
Before we start things out, let’s use a different planning technique: simplify the problem.
Let’s forget about the * for now and just print out # characters, however many the user-specified. Later we’ll
add the code for *.
Simplifying the problem allows you to more easily tackle it, and leads you to see ways to add the
missing features later.
The plan for this simplified phase isn’t that tough:
For however many numbers the user inputs:
Print a `#`.
We did a bit of magic there. We passed another argument to print() that told it we wanted it to put nothing
(an empty string, "") at the end of the line instead of the newline character it normally tacks on.
You could go crazy and say end="Beej" and it would put the word “Beej” after every hashmark. Do it. Go
crazy.
Getting there! But we’re not out of the woods yet. We need to make it so that for character more than 30
characters out, we print a * instead of a #.
Problem-solving step: Devising a Plan.
This is like the plan for printing the line from before, but we simplified that, remember? So we have to add
some complexity to meet the spec.
For however many numbers the user inputs:
If we're at the 30th character or earlier:
6.11. Exercises 39
Print a `#`.
Otherwise:
Print a `*`.
And that’s looking like a good case for if inside our for loop!
Problem-solving step: Carrying out the Plan.
Let’s add that if logic to the for loop at the end:
12 # Print the line
13 for i in range(x):
14 if i < 30:
15 print("#", end="") # Set the end-of-line character to nothing
16 else:
17 print("*", end="")
18
6.11 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
6
We could have also tested i <= 29.
7
Have two loops!
8
https://beej.us/guide/bgpython/source/examples/hashast.py
40 Chapter 6. Flow Control and Looping
1. Print out the sum of the numbers from 1 to (and including) 10000. (Solution9 .)
2. Print out values for x and x**4 for all x between 0 and 99, inclusive. (Solution10 .)
3. Ask the user to input a number, or the word quit. If the user enters a number, print out that number
times 10. If the user enters quit, the program should complete. (Solution11 .)
4. Prompt the user for two numbers. Print out all the odd numbers between and including those two
numbers. (Solution12 .)
5. Print out the numbers from 1 to 100. Except if the number is divisible by 313 , print Fizz instead. If
the number is divisible by 5, print Buzz instead. And if the number is divisible by 3 and divisible by
5, print FizzBuzz14 . There are a lot of ways to solve this one. (Solution15 .)
6. Make up two more exercises and code them up.
6.12 Summary
We covered all kinds of super-important things in this chapter.
• Flow Control
• Boolean algebra, conditional expressions, True, False
• if-else
• while loops and for loops
• A bit about testing edge cases
Guess what! You now know enough Python syntax to solve any computing problem! I’m not kidding16 !
See, it’s not knowing all the syntax that’s important; it’s the ability to figure out how to put it all together in
the right way.
That said, we haven’t learned enough Python syntax to necessarily make solving every computing problem
easy. In the upcoming chapters, we’ll learn more tools that Python gives you to increase the size of your
problem-solving toolkit.
9
https://beej.us/guide/bgpython/source/examples/ex_10ksum.py
10
https://beej.us/guide/bgpython/source/examples/ex_xfourth.py
11
https://beej.us/guide/bgpython/source/examples/ex_ntimes10.py
12
https://beej.us/guide/bgpython/source/examples/ex_oddsbetween.py
13
A number x is divisible by 3 if x % 3 == 0.
14
This is a famous interview problem for junior devs.
15
https://beej.us/guide/bgpython/source/examples/ex_fizzbuzz.py
16
https://en.wikipedia.org/wiki/Turing_completeness
Chapter 7
Strings
7.1 Objective
• Get a firm grip on what a string is
• Convert from other types to strings
• Concatenate strings
• Understand that strings are immutable
• Get individual characters with strings
• Slice a string
• for loop through a string
• Use basic string manipulation methods and functions
• Print strings using formatted output
Be sure to leave enough room for the maximum number of digits you’ll need in the largest product.
41
42 Chapter 7. Strings
You can also embed double quotes in double-quoted strings (or single quotes in single-quoted strings by
putting a backslash character in front of them (\) . This is called escaping the character, which means “Hey,
Python, treat this like a literal quote mark—just print a quote mark out,” as opposed to “Hey, Python, this is
the end of the string.”
'Beej\'s string' # Equivalent to the example, above
"Beej says, \"hi!\""
But wait—there’s clearly a constant string there, as well! The prompt "Enter a string:" is a string!
Strings everywhere!
Later we’ll learn about file and network I/O and how they’re used with strings and other data types. But for
now, we’ll stick to some basics.
Likewise, you can convert from strings to other types, like int and float with those respective functions:
7.6. String Concatenation with + 43
In this way, if you have a string with a number in it, you can convert it to a numeric value so that you can
perform math on it.
This is how you build smaller strings together into larger ones.
You often find the assignment-concatenation operator in use to add to the end of a string:
x = "B" # start with "B"
x += "e" # add an "e" to the end of the string
x += "e" # add an "e" to the end of the string
x += "j" # add a "j" to the end of the string
x += "!" # add an "!" to the end of the string
print(x) # Beej!
Let’s take a close look at that. It’s telling us that on line 3 of foo.py, where we have print(x + y) we’re
getting this error:
TypeError: can only concatenate str (not "int") to str
y is an int, but x is a str. This error is telling us that we can’t concatenate an int onto a str. What to do
now?
44 Chapter 7. Strings
Success!
Problem-solving step: Looking Back.
Any other ways to solve this? We could have done the str() call later:
x = "Hello"
y = 3490
print(x + str(y)) # Hello3490
When reading this code, x[1] would be read aloud as “x sub 1”, a nod to classic mathematical notation 𝑥1 .
The 1 in this case is called the index into the string.
Really important: index numbers start at 0!! The first character in a string is sometimes referred to in speech
as the zeroth character and the second character is sometimes referred to as the oneth character, and twoth,
and threeth, and so on, in an attempt to avoid ambiguity. Say “The character at index 3” if you want to be
sure.
Fun Indexing Fact: every programming language in serious use today uses 0-based indexing
(that is, indexes start at 0). There are some useful mathematical implications for doing so, even
if it’s trickier to think about.
Do some experimentation here. Try getting characters past the end of the string? What happens? (We’ll
learn to mitigate this later.)
What if you try a negative index? What do you think will happen? What does happen?
Turns out if you specify a negative index in Python, it gets the character starting from the end of the string!
7.9. Slices 45
x = "Beej!"
7.9 Slices
Problem-solving step: Understanding the Problem.
A slice is part of a string. You specify them by knowing the starting index and ending index into a string, and
separating them by a colon :.
x = "Beej!"
print(x[1:4]) # "eej"
The slice starts at the first index number and stops just before the second index number. (Remind you of
anything? Yes—just like range()!)
In this way, you can pull out any substring from a string.
Easy peasy!
Problem-solving step: Looking Back.
What could we have done better?
46 Chapter 7. Strings
Also, we’re not actually enforcing the user to enter at least 3 characters. How would we do that? Remember
how we used a while loop before to verify input? We could do the same.
But how can we tell if a string is at least a certain length? There are a couple of ways. Turns out, your slice
will be an empty string ("") if the length of the string is less than 3, and you could use that to detect.
In a bit, we’ll also discuss the len() function that will give you the length of any string you pass in.
print(x) # hello
print(y) # hello world
See in that example how the value of x is unchanged? We couldn’t change it if we wanted to. Check this out:
x = "hello"
print(x[2]) # print character 2, namely "l"
x[2] = "z" # ERROR! Python won't allow you to change the string!
If you wanted to make a string where character number 2 is swapped out, you’ll have to slice it up and build
it yourself.
x = "hello"
y = x[:2] + "z" + x[3:] # Make a new string
print(y) # hezlo
Or you could use regular expressions1 or some other string methods to replace the letter… but remember
that these methods produce a new string—they have to since strings are immutable!
It’s the same story with numbers, although this is behavior that you might take for granted, it’s so expected.
x = 12
y = x + 2 # This creates a new number--it doesn't change 12
1
We’ll talk about regular expressions, or regexes, later.
7.12. for-loops with Strings 47
print(x) # 12
print(y) # 14
Like I said, so far all the types we’ve learned about are immutable. But later, we’ll talk about lists, dictionaries,
and sets, which are the three mutable types in Python.
So remember: any time you think you are “changing” a string, you’re actually making a completely new one.
It’s important to keep this model in mind because it will prevent all kinds of bugs and misunderstandings as
we progress.
for c in s:
print("character:", c)
If you run this, you’ll see it prints each of the characters in turn:
character: H
character: e
character: l
character: l
character: o
character: !
You can use this if you ever need to traverse a string a character at a time. Of course, if you only want to
traverse part of a string, you can slice it first!
Another little tidbit here that might be useful is the enumerate() function. This will return a series of
index-value pairs. That is, it returns both the index into the string and the character at that index.
s = "Hello!"
for i, c in enumerate(s):
print("character at index", i, "is:", c)
outputs:
character at index 0 is: H
character at index 1 is: e
character at index 2 is: l
character at index 3 is: l
character at index 4 is: o
character at index 5 is: !
That’s useful if you need to know the index and the character. More on the enumerate() function later.
Python has a lot of built-in functions to help you manipulate and use strings.
Here are a few of them:
Take note of the len() function—we’ll use that to tell us how many characters there are in a string.
But now I want to introduce a new term and style of coding that you’ll frequently encounter moving forward:
methods.
Methods are functions that work on a specific object. We’re getting ahead of ourselves with this “method”
and “object” talk, but for now think of them as functions that work on a specific string.
But isn’t that just like the functions we just saw?
Yes, you got me. But we use these differently! Yay! This will all make more sense someday in the future,
but bear with me for now.
Let’s look at an example with the .upper() method. (Usually pronounced “dot upper” or “upper method”.)
a = "Beej!"
b = a.upper()
print(b) # BEEJ!
Method Description
.split() Split a string into a list2 on the given string.
.strip() Strip whitespace3 from both ends of the string.
.upper() Convert string to all uppercase.
.lower() Convert string to all lowercase.
.replace() Replace all occurrences of one word with another in the string.
.find() Find the index of the substring in the string, or -1 if it’s not found.
.count() Count the number of occurrences of the substring.
.startswith() Return True if the string starts with the given string.
.endswith() Return True if the string ends with the given string.
.capitalize() Capitalize the first letter of each word in the string.
You can also chain them together and they evaluate in turn, left to right:
s = " another EXAMPLE! "
There are a whole lot of string methods4 you can use, more than we’re going to talk about here. But go peruse
them just so you have an idea of what you have at your disposal.
which works, but doesn’t offer as much control over the output.
Let’s take a look at something called F-strings which are new in Python 3.6. (“Formatting strings”.)
These offer us a really powerful method of formatting output. So powerful we’ll only be scratching the
surface here.
2
More on lists in upcoming chapters.
3
Spaces, tabs, and newlines.
4
https://docs.python.org/3/library/stdtypes.html#string-methods
50 Chapter 7. Strings
The gist is that we can make a new string where we inject the value of variables (or expressions) into a string
at a specific spot.
Simple example:
x = 10
print(f"x is {x}") # x is 10
1. There’s an f in front of the quotes. This signifies that this is an F-string, as opposed to a regular string.
2. We inject the expression to evaluate inside curly braces5 { and }.
Python automagically evaluates that expression and puts the result into the F-string at that point.
Here’s another:
x = 10
print(f"x plus 10 is {x + 10}") # x plus 10 is 20
We can also put a field width in there which controls how big the “cell” is in which the number is printed.
Compare this:
print(f"a number: {1000}")
print(f"a number: {50}")
print(f"a number: {250}")
which outputs:
a number: 1000
a number: 50
a number: 250
to this:
print(f"another number: {1000:4}")
print(f"another number: {50:4}")
print(f"another number: {250:4}")
which outputs:
another number: 1000
another number: 50
another number: 250
The :4 says to output the expression in a 4-space-wide field. This gives us a great way to make columns
align on subsequent rows, like if you were printing out a spreadsheet.
Another thing it can do is specify a number of decimal places to print out floating-point numbers.
x = 3.1415926
print(f"Pi is {x:.2f}") # Pi is 3.14
That format string says “print x as a floating-point number, with 2 decimal places”.
F-strings are really powerful when it comes to controlling your output. We’ll explore more as we go.
5
Also called squirrely braces.
7.15. Chapter Project 51
This form has fallen out of favor due to the popularity of F-strings.
It’s even farther out of favor. F-strings are the new thing.
Fun Computing History Fact: printf() is a function in the C programming language that was
considered so awesome that the creators of Python decided to immortalize it with the % operator
that does the same thing. And even now, the format specifiers used in F-strings to describe the
type of data being printed match those from the C language. Not bad for a language invented
in the 1970s, eh?
Note that the printf % operator is the same as the arithmetic modulo (remainder) operator %. Python looks
at the arguments to the operator and does the right thing. If the left argument is a string, it’s printf. If it’s a
number, it’s modulo.
(If you’re rusty on your multiplication tables, what you do to multiply 3 × 4 is to look up 3 along the top
edge, then look up 4 along the left edge, and then look in the table where they cross. There you’ll find 12,
the answer.)
How can do we attack this? Let’s look at a couple of things.
First, look for patterns. See any?
I’ll wait while you look.
There are several in there.
• The diagonals read 1, then 2 2, then 3 4 3, then 4 6 6 4… it’s symmetric.
52 Chapter 7. Strings
• The first row is 1 2 3 4, and the second is 2 4 6 8, and the third is 3 6 9 12. The first skips by 1, the
second by 2, the third by 3.
• Columns do the same as rows in terms of numbers skipped.
Can we use any of those to our advantage? Maybe…!
Second, let’s try to simplify the problem.
What if the problem were to print:
1 2 3 4
But of course, we don’t just want to print rows three times… the end result is going to have the number of
rows that the user input. We need to loop to make it happen. A loop of loops! A nested loop!
Problem-solving step: Devising a Plan.
Before we jump into this, I’d like you to take the time to think about this. Set a timer and work on it for 3
minutes.
Do it.
Timer. Do it. You will gain valuable experience points for the attempt.
I’ll be back in 3 minutes.
[Elevator music—do it now!]
Okay, I’m back. What did you come up with?
I’ll go over one solution here, but if you came up with one and it’s different, don’t worry! There are no wrong
answers here. There are only answers that you, personally, like better or worse than others. Part of being
skilled in the art is that you can get better at making these assessments as you progress.
One possible plan for this is to have an inner loop (the nested loop) that prints a complete row out. In other
words, it’s printing out all the columns in the row. And then outside of that, we have the outer loop that is
responsible for printing out a bunch of rows.
And then inside there, we have to compute what values to print for each row, based on which row we’re on.
7.15. Chapter Project 53
First, we have to get user input. Let’s do that, and validate that it’s between 1 and 19 similar to the last
project.
while input isn't between 1 and 19:
ask the user for input
Of course, printing the value for the column is the tricky bit.
Remember when we did our sample with three hardcoded loops, above?
for i in range(1, 5, 1): print(...)
for i in range(2, 10, 2): print(...)
for i in range(3, 15, 3): print(...)
See any patterns in there? 1 2 3 is a pretty easy one. But what about that 5 10 15? Clearly, it’s multiples of
5, but where does “5” come from?
In that example, we were printing rows and columns from 1 to 4. (That is, the user inputs the number 4.)
And 5 is one more than 4. That’s a pattern. Is it the right one? Let’s try!
If we take the first 3 numbers in the ranges (that is, 1 2 3), and multiply by 4+1, we end up with 5 10 15, just
like we want.
So there’s a formula in there for that middle number in the range. Assuming the user enters x as the value,
then the middle number for any range() call would be:
row_number * (x + 1)
and the first and last numbers in the range would simply be the row number!
The last thing we need to do is make sure we have the times table all formatted correctly so that the columns
line up. The biggest number we’ll print is 361 (19 × 19) so we’ll need a space between columns and three
spaces for the number. We can use an F-string with a field width of 4 to make this happen.
Problem-solving step: Carrying out the Plan.
Let’s start by entering a number and make sure this works:
1 valid_input = False
2
We just loop until we get valid input, just like in the last project.
Now for the times’ table part. We want to go from 1 to the number entered, so we’ll start at 1 and go to x +
1.
And then we’ll compute the max number for each row, just like we planned, above. And we’ll print the
value!
54 Chapter 7. Strings
One gotcha is that we want to print a bunch of things on the same line and print() goes to the next line by
default. We’ll use our friend end="" in the print() call to keep it on the same line, and then add another
empty print() after the loop to go to the next line.;
(Continuation of the code above!)
10 for row in range(1, x + 1):
11 for product in range(row, row * (x + 1), row):
12 print(f"{product:4}", end="")
13 print()
Woot!
Did you have another solution that worked? There are plenty others!
Problem-solving step: Looking Back.
What are the corner cases that you should test? (Look for the if statements, and test on either side of those.
0 and 1 and 19 and 20.)
If you didn’t come up with a different solution, try to do so now. What if you used while loops instead of
for loops?
(Solution6 .)
7.16 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
A lot of these can use for loops in the solution! Use any knowledge you have to solve these, not only what
you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. Given the following varibles:
x = 3490
y = 3.14159
(Solution7 .)
6
https://beej.us/guide/bgpython/source/examples/multtable.py
7
https://beej.us/guide/bgpython/source/examples/ex_fstring.py
7.17. Summary 55
(Solution8 .)
3. Using this string, create a copy of it where all the vowels are uppercase and all the consonants are
lowercase.
s = "The quick brown fox jumps over the lazy dogs."
Hint: think like a human. If you had a physical set of blocks with letters on them in front of you, what
would be the process and steps for building a new string with the required changes?
(Solution9 .)
4. Allow the user to input a string, and also a number. Print out the character at that index number in the
string. Don’t allow the user to enter a number that’s out of range.
(After you solve this, check out the solution10 for a twist on checking for valid input.)
5. Allow the user to input a string, and also two numbers. Print out the slice from those index numbers
in the string. Don’t allow the user to enter numbers that are out of range.
(In the solution11 , there’s duplicated code to enter two numbers. Later, when we get to functions, we’ll
learn how to remove this duplicated code.)
6. Makeup two more exercises and code them up.
7.17 Summary
In this chapter, we did all kinds of crazy things with strings.
• Conversions from other types
• How to concatenate strings with +
• How to get characters and slices out of a string
• How for loops process strings
• Learned a bunch of string methods and functions
• Formatted output with F-strings
Coming up, we’re going to learn even more built-in data types that we can use. After that, we’ll talk about
functions, and then you’ll be dangerously close to being able to write real programs!
8
https://beej.us/guide/bgpython/source/examples/ex_goatcount.py
9
https://beej.us/guide/bgpython/source/examples/ex_uppervowel.py
10
https://beej.us/guide/bgpython/source/examples/ex_charat.py
11
https://beej.us/guide/bgpython/source/examples/ex_sliceat.py
56 Chapter 7. Strings
Chapter 8
Lists
8.1 Objective
• Understand what lists are
• Understand how assignments with mutable types work
• Access individual elements and slices in a list
• Iterate over lists with for
• Use common lists built-in functions
• Construct new empty lists of repeating fixed values
• Construct new lists with list comprehensions
• Construct and use lists of lists (2D lists)
Allow the user to navigate a maze by entering directions to move (n, s, w, or e). They can also enter q to
quit.
The maze should look like this, with . representing a space the player can move into, and # representing a
wall. An @ symbol indicates the player’s current position.
57
58 Chapter 8. Lists
Remember how regular variables hold one thing? Well, lists are variables that can hold a lot of things.
Fun Lists Fact: Most other languages have a different name for lists: they call them “arrays”.
Same thing.
But wait—if a list can hold a lot of things, how do we differentiate? How do I tell Python that I want the
second thing in the list? Or the fifth thing?
Luckily it’s easy enough; we just have to specify the index into the list that holds the thing we want.
Think of it as a row of postboxes, numbered starting from 0, then 1, then 2, and going on up to however many
postboxes we have. Each postbox can hold a thing, and you can refer to it by giving the postbox number.
There’s a list. We know it’s a list because of the square brackets around it. It’s a list of four integers.
Let’s print out the zeroth element in the list. We do this by using square brackets after the list variable name
and giving the index inside those brackets. Does this look familiar? It’s the same syntax we used to get
individual characters out of strings!
print(x[0]) # prints 10
Do you remember that strings had a cool trick where you could use a negative index to refer to characters
from the end of the string? We can do the same thing with lists!
print(x[-1]) # prints 9, the element at the end of the list
Remember that indexes start at zero, again, just like with strings.
Keeping with the “things you can also do with strings” theme, you can also slice an array, just like a string.
a = [1, 2, 3, 4, 5]
print(b) # [2, 3, 4]
You can also set individual elements, leaving the rest of the list unchanged:
x[1] = 99
This brings us to a stark difference between lists and strings: lists are mutable. You can change individual
elements inside the list without creating a new list! Remember with strings, you couldn’t change them—you
could only make new ones.
It’s such a key difference, we’re going to talk about it in detail now, and then again later. This is a big source
of confusion among new developers.
8.4. List Assignments 59
In that example, as we learned earlier, there is one string "Hello" in memory, and both x and y refer to it.
Strings are immutable. So you can’t do something like this:
x = "Hello"
y = x
But lists are mutable. We can change something that’s in a list. Let’s do an analogous example:
x = ["A", "B", "C", "D"]
y = x
print(x[2]) # "Z"
print(y[2]) # "Z" also!!!
Wait—what happened there? We changed the value in the second index of x, but it also changed it in y! How
did that happen?
Remember: it’s because when we did:
x = ["A", "B", "C", "D"]
y = x
both x and y came to point to the same list. There is only one list. Both x and y refer to it. So if you change
that one list, you see that change reflected in both x and y.
(If you could change a string, it would work the same way. But you can’t because it’s immutable.)
Mutable types include the following (some of which we haven’t talked about yet):
• Lists
• Dictionaries
• Objects1
• Sets
In some languages, types that appear to get copied on assignment (like strings and integers)
are called value types. Whereas types that can be referred to by multiple variables through
1
Technically, lists and dictionaries are objects, so we’re being a bit redundant.
60 Chapter 8. Lists
assignment (like lists) are called reference types. Python doesn’t make this distinction, although
you might hear this phraseology used in the wild.
What if you want a copy of a list, and not just a copy of the reference? You can force a list copy a number of
ways, but these are three common ones:
b = a.copy() # Copy with .copy() method
b = list(a) # Copy with the list() function
b = a[:] # Copy by slicing the entire list
Even if you don’t have it quite down yet, don’t worry. We’ll hit this topic a few more times as we progress.
for i in x:
print(f"element is: {i}")
for i in range(4):
print(f"element is: {x[i]}")
Although that’s not idiomatic Python3 (the first example is better), it demonstrates how to use a variable as
the index. We refer to x[i] inside the loop, and then have i change to loop over every element’s index.
It’s irking me that we have that hard-coded 4 in the range(). It only works for lists of length 4. Let’s see if
we can fix it.
Sneak preview: you can get the number of elements in a list with len().
Let’s make the range() go up to “the length of the list” instead of to 4:
x = [11, 55, 33, 99]
2
Technically we can use it to iterate over anything that’s iterable, which is quite a number of things.
3
Idiomatic means “the standard, accepted way of doing a thing in a language”.
8.6. for and enumerate() 61
for i, v in enumerate(x):
print(f"The element at index {i} has value {v}")
after processing and doubling all the even values, it will be:
[1, 4, 3, 8, 5, 12]
Do you remember how to take the remainder in Python? With the modulo operator: %.
x = 12
if x % 2 == 0:
print("x is even!")
62 Chapter 8. Lists
else:
print("x is odd!")
5 for i, v in enumerate(x):
6
9 if v % 2 == 0: # check if v is even
10
11 # double the value and store it at the same place in the list
12 x[i] = v * 2
13
4
https://beej.us/guide/bgpython/source/examples/listdouble.py
5
Remember that a method is a function that you call on a particular object with the dot (.) operator.
8.8. Built-in Functions for Lists 63
Function Description
len(a) Return the number of elements in the list
enumerate(a) Iterate over index/value pairs in the list
a.append(x) Append variable x to the end of the list
a.clear() Clear all elements from the list
a.copy() Make a copy of the list
a.count(v) Count the number of occurrences of v in the list
a.extend(b) Add elements of list b to end of list a
a.index(v) Return the first index of v in list a
a.insert(i,v) Insert v in list a before index i
a.pop() Remove and return the last element in a
a.pop(i) Remove and return the element at index i in a
a.reverse() Reverse the elements in the list
a.sort() Sort the list
Let’s just fire up the editor and start messing around with these to see how they work.
Here’s a program called listops.py6 that does just that. You should also experiment with variations of
these to get a feel for them:
1 a = [5, 2, 8, 4, 7, 4, 0, 9]
2
5 a.append(100)
6
15 print(v) # 100
16 print(a) # [5, 2, 8, 4, 7, 4, 0, 9]
17
20 print(a) # [9, 0, 4, 7, 4, 8, 2, 5]
21
26 b = [1, 2, 3]
27
6
https://beej.us/guide/bgpython/source/examples/listops.py
64 Chapter 8. Lists
33
In addition to those functions, the + operator will take two lists and concatenate them together into a third
list:
a = [1, 2, 3]
b = [4, 5, 6]
Look at the amount of control we have over lists now! Not only can you read and write values at specific
list indexes, but you can add to the end, insert stuff in the middle, remove from the end, or from anywhere
within the list.
You are All Powerful!
Okay, maybe not, but at least you can do a thing or two with lists.
Sometimes we know those lists upfront, and other times we compute them as we go.
Spoiler alert!
Each number is the sum of the previous two numbers.
0 + 1 = 1, 1 + 1 = 2, 1 + 2 = 3, etc.
But what about the first two numbers in the sequence
Those are given to be 0 and 1, no questions asked. This way you always have at least two previous numbers
to get the next one.
What we want to do is write a program that builds and prints a list containing the first 100 Fibonacci numbers.
We don’t want to do any of the addition ourselves—we want the code to compute it for us.
Problem-solving step: Make A Plan.
We’re going to need a list to hold all the numbers.
We have the first two numbers (0 and 1), so we can put those in the list.
Then we have to look at the previous two numbers from the end, add them together, and then append the sum
to the end of the list.
And we have to do that 98 more times to get 100 numbers.
Doing something 98 times seems like a for-range() loop to me.
We can append with the .append() method.
We can get the last and previous-to-last elements in the list with negative list indexes.
initialize the list with [0, 1]
for 98 times:
compute the sum of the previous two numbers
append sum to the list
4 # for 98 times
5 for _ in range(98):
6
And you’ll get some output that looks vaguely like this (I’ve rewrapped the output here—yours might not be
so pretty):
7
https://beej.us/guide/bgpython/source/examples/fiblist.py
66 Chapter 8. Lists
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597,
2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418,
317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465,
14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296,
433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976,
7778742049, 12586269025, 20365011074, 32951280099, 53316291173,
86267571272, 139583862445, 225851433717, 365435296162, 591286729879,
956722026041, 1548008755920, 2504730781961, 4052739537881,
6557470319842, 10610209857723, 17167680177565, 27777890035288,
44945570212853, 72723460248141, 117669030460994, 190392490709135,
308061521170129, 498454011879264, 806515533049393, 1304969544928657,
2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464,
14472334024676221, 23416728348467685, 37889062373143906,
61305790721611591, 99194853094755497, 160500643816367088,
259695496911122585, 420196140727489673, 679891637638612258,
1100087778366101931, 1779979416004714189, 2880067194370816120,
4660046610375530309, 7540113804746346429, 12200160415121876738,
19740274219868223167, 31940434634990099905, 51680708854858323072,
83621143489848422977, 135301852344706746049, 218922995834555169026]
and so on. We’re constructing the list as we go, building it from the previous elements8 .
Another thing I did in the for loop was use _ as the looping variable name. That’s a perfectly legitimate
variable name9 .
By convention, _ is used as a name when you don’t intend to use it elsewhere.
You must have a variable named in your for loop—no option not to. But using _ for that name indicates to
programmers that you are just using the loop to count, and you don’t actually care what the count is.
You can multiply a list by a constant value to get a new list repeated that many times.
What?
8
We’re using a technique here generally called bottom-up dynamic programming. But that’s a story for another time. Probably
involving Fibonacci again.
9
Names can be made up of letters, digits, and underscores, as long as they don’t start with a digit.
8.12. List Comprehensions 67
Easier demonstrated:
a = [11, 99]
b = a * 3
It just repeats the list that many times into a new list.
A very common use of this is to create a new list of a certain number of elements, initialize to zero:
a = [0] * 10
print(a) # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
While we’re on about esoteric list declarations, you can declare a list of no elements like so”
a = []
for v in a:
if v % 2 == 0: # if v is even
new_list.append(v * 4)
Leaving the filter off makes the list unconditionally. For example, all the numbers in the list times 4:
# result loop
# |----| |--------|
new_list = [v * 4 for v in a]
It’s important to note that when we assign into z, we’re not copying lists x and y. What we’re
doing is making it so that the values in list z refer to the same lists as x and y do.
In other words, there’s only one list in memory with the values [1,2,3]. And is referred to by
both x and z[0]. Both variables reference the same list.
In that example, how could we access elements of the lists-in-list?
We’re going to use square bracket notation again, but even more so.
8.14. Chapter Project Implementation 69
x = [1, 2, 3]
y = [4, 5, 6]
z = [x, y]
So far so good?
Here’s the thing to notice: since z[0] and z[1] are lists, you can access the elements within those lists by
using square bracket notation again.
Let’s try to get the number 6 out of that second list.
print(z[1][2]) # prints 6
(While Python doesn’t mind if you use parentheses like this, programmers don’t do it since it makes the code
look messy.)
But what does this buy us? Lists of lists are exciting and all. (Right?) What are they useful for?
Having a list of lists literally adds a second dimension to the data you can represent. With a single list, you
can represent one “row” of data. With a list of lists, you can represent multiple rows or a grid of data.
What are some places in computing a grid or multiple rows of data are used?
Spreadsheets! What else? See if any other ideas come to mind.
For declaring lists of lists, it’s really common to just declare them all at once, and not use an intermediate
variable to represent the sublists.
For example, the previous list we were using, above, could be declared more simply like so:
z = [
[1, 2, 3],
[4, 5, 6]
]
or, if it looks better and your style guide allows it, you can put it on one line:
z = [ [1, 2, 3], [4, 5, 6] ]
print(z[0][1]) # Prints 2
Now let’s see if we can put it all together for this chapter’s project!
That makes this project more difficult than the previous ones. We’re going to break down the problem and
decide what tools we know that we can bring to bear to solve it.
And this is what being a software developer is all about.
I’m not expecting the answer to be obvious. You’ll rarely see a problem that has an obvious solution, even
as a seasoned developer. But we do have our problem-solving framework to break down the problem into
workable parts. So let’s do it!
Problem-solving step: Understanding the Problem.
We want to do several things with this project:
• Print a map on the screen
• Get user input
• Use the input to move a player indicator around the map
• Make sure the player doesn’t move through walls
• Keep repeating until the player quits
What’s missing from the spec that we need to know?
Remember your compass directions?
N
|
W --+-- E
|
S
Now’s the time to get the answers to questions clarified. Much easier to do it now than after you’ve coded
up the wrong thing!
What if the user provides invalid input? What happens then is missing from the spec. For that, let’s print an
error message:
Unknown command: {x}
Let’s start with high-level pseudocode, and then break it down where required.
while not quit:
print map and player indicator
get input
make sure input is valid
make sure we're not moving through walls
How do we know that breaking down subproblems will be useful with this pseudocode? The first clue is
that some of the steps are substantial, e.g. “print map and player indicator” immediately brings to mind the
question, “How the heck can we do that?”
If any steps are too complex or are unclear, it means you have to break them down further. Let’s do that for
all the unclear sections:
while not quit:
print map and player indicator
for each row of the map:
for each column of the map:
if this is where the player is:
print @
else:
print the map character
get input
make sure input is valid
if input invalid:
print error message
else:
figure out the new row and column of the player
#####################
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
8.14. Chapter Project Implementation 73
8 "#...#..........#....#",
9 "#...#..........#....#",
10 "#..............#....#",
11 "#..............#....#",
12 "#####################"
13 ]
We’ve split the map list into multiple lines to make it easier to read.
Now we need to print it out. In our pseudocode, we used a nested for loop with if conditions.
1 # The map
2
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
8 "#...#..........#....#",
9 "#...#..........#....#",
10 "#..............#....#",
11 "#..............#....#",
12 "#####################"
13 ]
14
There are a couple of things to note there, so make sure to digest the code. We’re going through each row,
and for each row, we’re going through each column and printing the character.
We want the characters to all print on the same line for a given row, so we use the end="" trick to keep
Python from going to the next line.
And at the end of each row, we have an empty print() to get the cursor down to the next line for starting
to print the next row.
And when we run that, we get the map printed out!
But we don’t have the player position stored anywhere, and we’re not showing it on the screen. Let’s add
that next.
1 # The map
2
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
8 "#...#..........#....#",
9 "#...#..........#....#",
10 "#..............#....#",
11 "#..............#....#",
74 Chapter 8. Lists
12 "#####################"
13 ]
14
15 # Player position
16
22 # Use enumerate() to get the row and column indexes for the if:
23 for row_index, row in enumerate(map_data): # for each row
24 for col_index, map_character in enumerate(row): # for each col
25 if row_index == player_row and col_index == player_column:
26 print("@", end="") # end="" no newline
27 else:
28 print(map_character, end="")
29 print()
30
So there we’ve added a couple of variables to store where the player is, and then in the map printing loop,
we check to see if this location is where the player is. If it is, print an @, otherwise print the map character.
For the next small thing to add, let’s get user input and quit if the user enters “q”. Otherwise, we’ll print the
map again in a loop.
1 # The map
2
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
8 "#...#..........#....#",
9 "#...#..........#....#",
10 "#..............#....#",
11 "#..............#....#",
12 "#####################"
13 ]
14
15 # Player position
16
17 player_row = 4
18 player_column = 9
19
20 quit = False
21
30 else:
31 print(map_character, end="")
32 print()
33
34 # Get input
35
38 if command == "q":
39 quit = True
40 continue # jump right back to the top of the while
41 else:
42 print(f'Unknown command {command}')
Getting there!
Something new to note! There’s a continue statement on line 40. This causes program execution to jump
back to the top of the while loop, ignoring the rest of the loop body. It means, “Don’t do anything else in
this block—just short circuit back to the while condition. (Which tests to false immediately and exits the
loop.)
So now we have the player position being printed, and we have the user inputting a command. However, we
still need to handle the directional commands and actually move the player around.
We plan to compute the new position for the player based on the current position and the user input. For
example, if the user goes north (up) on the screen, the player’s column stays the same, but the row number
decreases by 1.
1 # The map
2
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
8 "#...#..........#....#",
9 "#...#..........#....#",
10 "#..............#....#",
11 "#..............#....#",
12 "#####################"
13 ]
14
15 # Player position
16
17 player_row = 4
18 player_column = 9
19
20 quit = False
21
34 # Get input
35
That’s working great, but we can still walk through the walls. Let’s change those last few lines of the program
to verify that the new position is an empty room before we move the player in there. (Note the line numbers!)
58 if map_data[new_row][new_column] != ".":
59 print("You can't move that way!")
60 else:
61 # Set the current position to the new position
62 player_row = new_row
63 player_column = new_column
Just back to “Understand the Problem” and implement some of those things.
Also, the game looks neater if you clear the screen before printing the map, but unfortunately there’s no easy
way to do this in a cross-platform manner11 . But there is a hacky thing we can do.
If your terminal obeys ANSI escape codes12 , which is likely, we can send special sequences of characters to
it to clear the screen then home the cursor (move it to the top left).
The magical incantation looks like this:
print("\x1b[2J\x1b[H", end="") # Clear the screen
Go ahead and tuck that up above where you start printing the map and you’ll see the effect. If your terminal
doesn’t support ANSI sequences, you’ll just see some weird characters. Bogus13 .
If you really want to get into character graphics, there’s a library you should try: curses14 . It allows you to
clear the screen, position the cursor, get input without echoing it to the screen or waiting for RETURN, output
in color, and more.
Although we have enough knowledge to add monsters and treasure and so on, it will be easier to do so once
we learn about dictionaries and objects in future chapters. We have more tools at our disposal!
(Solution15 .)
8.15 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. The following code prints out 99:
1 a = [1, 2, 3]
2 b = a
3
4 b[0] = 99
5
6 print a[0]
How can we change only line 2 so that b is a copy of a, causing the program to print out 1 instead?
(Solution16 .)
2. Write a loop that prints out the total sum of the following list:
[14, 31, 44, 46, 54, 59, 45, 55, 21, 11, 8, 34, 66, 41]
and write one line of Python that changes the list to:
[11, 22, 33, 99]
Then, finally, write another line that changes the list to:
[11, 33, 88, 99]
This exercise should manipulate the same list, not create new lists.
(Solution18 .)
4. Create the following list in under 20 characters of Python code:
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
(Solution19 .)
5. Using a list comprehension, make a new list from the following that only includes numbers that are
multiples of 5:
[14, 31, 44, 46, 54, 59, 45, 55, 21, 11, 8, 34, 66, 41]
(Solution20 .)
6. Using a list comprehension, make a new list from the following that only includes all-uppercase ver-
sions of all words that begin with a consonant.
["alice", "beej", "chris", "dave", "eve", "frank"]
Sample output:
['BEEJ', 'CHRIS', 'DAVE', 'FRANK']
(Solution21 .)
7. Write a program that generates a list of lists (2D list) containing a multiplication table up to 12 × 12.
You should be able to print the result of, say, 7 × 5 like so:
print(multtable[7][5]) # prints 35
(Solution22 .)
8.16 Summary
Look at all the stuff we’ve covered in this chapter!
• A brand new data structure to hold lists of information
• Understanding assignments with mutable types
• How to access and change items in a list
18
https://beej.us/guide/bgpython/source/examples/ex_listchange.py
19
https://beej.us/guide/bgpython/source/examples/ex_replist.py
20
https://beej.us/guide/bgpython/source/examples/ex_listcompx5.py
21
https://beej.us/guide/bgpython/source/examples/ex_listcompcap.py
22
https://beej.us/guide/bgpython/source/examples/ex_listmult.py
8.16. Summary 79
Dictionaries
9.1 Objective
• Understand what dictionaries are
• Initialize a dictionary
• Access individual elements in a dictionary
• Check to see if a key is in a dictionary
• Iterate over dictionaries with for
• Use common dictionaries built-in functions
• Construct new dictionaries with dictionary comprehensions
• Build Dictionaries of Dictionaries
• Understand that Dictionaries are Mutable
Mom Jorgensen:
Born: 1970
Mother: Grandma Jorgensen
Father: Grandpa Jorgensen
Siblings: [Auntie Jorgensen]
Dad Jorgensen:
Born: 1965
Mother: Granny Jorgensen
Father: Grandad Jorgensen
Siblings: [Uncle Jorgensen]
1
This is all about family trees. Did you know I’m related to Queen Elizabeth II (by marriage)? I’m her mother’s sister’s husband’s
father’s father’s sister’s daughter’s husband’s wife’s (drama!) sister’s husband’s father’s brother’s son’s son’s daughter’s son’s daughter’s
son. For realsies! I’m willing to bet that you’re related to Queen Elizabeth II, as well. That makes us cousins!
81
82 Chapter 9. Dictionaries
(Ok, I hear you saying, “Wait, your mom and dad were both Jorgensens? That’s suspicious. I mean, I’m not
saying, but I’m just saying.” Hold your tongue! I can assure you they’re not related2 .)
We want to write an app that will allow you to print out the birthdays of the parents of any given person.
Example:
Enter a name (or q to quit): Beej Jorgensen
Parents:
Mom Jorgensen (1970)
Dad Jorgensen (1965)
So we’ll need some way to store that, and some way to look through the data to get information about other
people who are referenced.
Yes, we could use lists of people and just search through, but there’s a less clunky way we might go about
doing this.
This chapter is all about how we might store such data.
That makes Python unhappy because "beej" isn’t an integer. And with lists, it wants integers.
But we can get around that limitation with dictionaries, or dicts for short.
Declaring a dictionary is a little bit different, but here’s a simple example to start:
d = {} # Squirrely braces, not square brackets!
d["beej"] = 3490
print(d["beej"]) # 3490
So very similar in usage, though the initial declaration of the variable is different than a list.
With dicts, the value in the square brackets is called the key, and the value stored there is the value.
# key value
# | |
# --+-- ---+---
d["goats"] = "awesome"
You use the key to lookup a value in the dictionary, or to set a value in the dictionary.
2
Except via Queen Elizabeth II, like the rest of us.
9.4. Initializing a Dictionary 83
The key can be any immutable type (e.g. integers, floats, strings). The value can be any type.
But you can also pre-initialize the dictionary with a number of keys and values:
d = {
"name": "Beej",
"age": 29, # ish
"favorite OS": "windows",
"no really, favorite OS": "linux"
}
If you come from a web background, you might have come across JSON3 -format data. The Python dictionary
is very similar in format, though not exactly the same.
if "a" in d:
print(f'key a\'s value is: {d["a"]}')
3
https://en.wikipedia.org/wiki/JSON
4
Time complexity enthusiasts will recognize this as 𝑂(1), or constant time.
84 Chapter 9. Dictionaries
if "x" not in d:
print('There is no key "x" in "d"')
There is also a way to get an item out of a dictionary using the .get() method. This method returns a default
value of None5 if the key doesn’t exist.
val = d.get("x")
You can also detect a non-existent key with try/catch. But that’s a story for another time.
for k in d:
print(f"key {k} has value {d[k]}")
Note that in the latest version of Python, the keys come out in the same order they’ve been added to the
dictionary.
If you want them in another order, there are options:
d = {
"c": 10,
"b": 20,
"a": 30
}
And now we have this, where the keys have been sorted alphabetically6 .
5
If you haven’t seen it before or need a refresher, this is a value that represents “no value”. It’s a placeholder (what we call a sentinel
value) to indicate a no-value condition.
6
Or what we call lexicographically sorted. It’s like alphabetical, but on steroids so that it can handle letters, numbers, punctuation
and so on, all of which are all numbers deep down.
9.8. Common Built-in Dictionary Functionality 85
Also, similar to how lists work, you can use the .items() method to get all keys and values out at the same
time in a for loop, like this:
d = {
"c": 10,
"b": 20,
"a": 30
}
for k, v in d.items():
print(f"key {k} has value {v}")
Method Description
.clear() Empty a dictionary, removing all keys/values
.copy() Return a copy of a dictionary
.get(key) Get a value from a dictionary, with a default if it doesn’t exist
.items() Return a list-ish of the (key,value) pairs in the dictionary
.keys() Return a list-ish of the keys in the dictionary
.values() Return a list-ish of the values in the dictionary
.pop(key) Return the value for the given key, and remove it from the dictionary
.popitem() Pop and return the most-recently-added (key,value) pair
We already saw use of .get(), earlier, but it can also be modified to return a default value if the key doesn’t
exist in the dictionary.
d = {
"c": 10,
"b": 20,
"a": 30
}
We’ve already seen a use of .items(), above. If you want to see just all the keys or values, you can get an
iterable back with the .keys() and .values():
d = {
"c": 10,
"b": 20,
"a": 30
}
86 Chapter 9. Dictionaries
for k in d.keys():
print(k) # Prints "c", "b", "a"
for v in d.values():
print(v) # Prints 10, 20, 30
If you have a list of key/value pairs, you can read those into a dictionary pretty easily, too.
a = [["alice", 20], ["beej", 30], ["chris", 40]]
d = { k: v for k, v in a }
Nesting dictionaries like this can be a really powerful method of storing data.
9.11. Dictionaries are Mutable 87
print(d["beej"]) # 3491
That first way is preferred because it’s easier to read, and easy to read code is Happy Code™.
Not only that, but we can store all of those dicts in another container dict which uses the person’s name as
the key!
tree = { # Outer dict holds records for all the people
"Beej Jorgensen: { # Inner dict holds details for each person
"born": 1990,
"mother": "Mom Jorgensen",
"father": "Dad Jorgensen",
"siblings": [
"Brother Jorgensen",
"Sister Jorgensen",
"Little Sister Jorgensen"
]
88 Chapter 9. Dictionaries
}
}
So that looks like a reasonable approach to storing data. We can just add the other people to the dictionary
at that outermost layer.
Not only that, but we now have part of the problem solved. If the user enters “Beej Jorgensen”, all we have
to do is look that directly up in the outer dict, and then we can print out my parents’ names!
Of course, we’re still missing out on printing their birth years, but let’s tackle the smaller problem first, and
then figure out how to extract that missing data.
We’ll come back to the “Understanding the Problem” step in a while to revisit that.
We’ll start with a simple tree of just a single person. Let’s keep it as simple as possible, and then go from
there.
1 tree = {
2 "Beej Jorgensen": {
3 "born": 1990,
4 "mother": "Mom Jorgensen",
5 "father": "Dad Jorgensen",
6 "siblings": [
7 "Brother Jorgensen",
8 "Sister Jorgensen",
9 "Little Sister Jorgensen"
10 ]
11 }
12 }
13
And now let’s add some code to get the person’s name, or quit if they enter “q”:
14 done = False
15
19 if name == "q":
20 done = True
21 continue # Jump back to the top of the loop
22
And, finally, let’s print out the person’s name and their parents’ names:
23 record = tree[name] # Look up the record in the outer dict
24
28 print("Parents:")
29 print(f' {mother_name}')
30 print(f' {father_name}')
$ python3 familytree.py
Enter a name (or q to quit): Beej Jorgensen
Parents:
Mom Jorgensen
Dad Jorgensen
Enter a name (or q to quit): q
We’re still missing the parents’ birth years, but, as I said, we’ll tackle that later.
What happens if we run it with an unknown name? Remember that when you’re testing your code, you
should think like a villain and enter the most unexpected things you can.
Let’s try it with someone it doesn’t know.
$ python3 familytree.py
Enter a name (or q to quit): Arch Stanton
Traceback (most recent call last):
File "familytree.py", line 23, in <module>
record = tree[name] # Look up the record in the outer dict
KeyError: 'Arch Stanton'
Well, that’s ugly. It would be much nicer to print out some kind of error message.
What does the spec say we should do?
…nothing! It says nothing about this case! That’s not useful! The spec is missing information!
True, it is.
Turns out, this is a really common thing when programming. Your boss asks you to implement a thing but
doesn’t fully define what that thing is. It’s not that your boss is bad at this; it’s just that writing down the
exact spec and not leaving anything out is hard.
I promise you that if I asked you to write out the rules to Tic-Tac-Toe7 , I’d find something you left out. (“You
never said my ‘X’ could only take up one grid square!”)
The right thing to do at this point is to go back to the creator of the specification and ask exactly what should
happen in this case.
Problem-solving step: Understanding the Problem (again)
“Hey, specification writer! What do we do if the person doesn’t exist in the data?”
Answer: print out an error message like this:
No record for "Arch Stanton"
All right!
Problem-solving step: Devising a Plan (again)
We’re using this code to get a person’s record:
record = tree[name] # Look up the record in the outer dict
but as we see, that throws an exception if name isn’t a valid key in the dict.
How can we handle that? There are a couple of ways. One of them involves catching the exception, but
we’ll talk more about that in a later chapter.
7
That’s Noughts and Crosses, to some of you.
90 Chapter 9. Dictionaries
Something we can do that we discussed earlier in this chapter is to use the .get() method on the dict. This
will return the record, or None if the key doesn’t exist in the dict. Then we can test for that and print out
some error messages.
Problem-solving step: Carrying out the Plan (again)
23 record = tree.get(name) # Look up the record in the outer dict
24
32 print("Parents:")
33 print(f' {mother_name}')
34 print(f' {father_name}')
Now we’re getting pretty close. But we still are missing one big piece: the birth years of the parents.
Getting their names was cake: it was right there in the record for the person we’re looking up. But their birth
years aren’t in there.
How do we get them?
Problem-solving step: Devising a Plan (again)
We have the names of the parents. That’s it.
How do we go from a name to a birth year?
Looks like “Beej Jorgensen” has a birth year listed in his record…
We should add records for “Mom Jorgensen” and “Dad Jorgensen” and then they can have their own birth
years.
But the question still remains: how can we go from the user-entered “Beej Jorgensen” to the birth years for
his parents?
What we’re doing here is trying to tie one piece of data (“Beej Jorgensen”) to other pieces of data (1965 and
1970, his parents’ birth years.)
This is super common in programming. “How do I get from x to y?” We need to find the path.
So let’s see… we have Beej Jorgensen there, with his parents’ names listed.
That’s a start. But given his parents’ names, how do you get his parents’ birthdays?
Yes! You just take their names and look them up in the dictionary!
Except we haven’t added them yet. Let’s do that now. (Note that program line numbers, below, are reset at
this point.)
1 tree = {
2 "Beej Jorgensen": {
3 "born": 1990,
4 "mother": "Mom Jorgensen",
5 "father": "Dad Jorgensen",
6 "siblings": [
7 "Brother Jorgensen",
8 "Sister Jorgensen",
9 "Little Sister Jorgensen"
9.12. The Chapter Project 91
10 ]
11 },
12 "Mom Jorgensen": {
13 "born": 1970,
14 "mother": "Grandma Jorgensen",
15 "father": "Grandpa Jorgensen",
16 "siblings": ["Auntie Jorgensen"]
17 },
18 "Dad Jorgensen": {
19 "born": 1965,
20 "mother": "Granny Jorgensen",
21 "father": "Grandad Jorgensen",
22 "siblings": ["Uncle Jorgensen"]
23 }
24 }
25
31 if name == "q":
32 done = True
33 continue # Jump back to the top of the loop
34
Except now, when we print out the parents, we have to look up the mother’s and father’s record.
44 # Get the parent records
45 mother_record = tree.get(mother_name)
46 father_record = tree.get(father_name)
47
59
60 print("Parents:")
61 print(f' {mother_name} ({mother_born_date})')
62 print(f' {father_name} ({father_born_date})')
That’s it!
Problem-solving step: Looking Back
What could we do better? What are the shortcomings of this app?
Look at the dictionary structure we used to store the data. How could that be better? Think of all the cases
that exist in family trees. Sure, we covered the common case, but what about kids who were adopted? How
do we model that? Divorces? Second marriages? It turns out that modeling a family tree is far more complex
than you might originally anticipate.
What if two people have the same name? In a real family tree, it’s entirely likely there could be multiple Tom
Jones8 in the family tree. But since we’re using the name as the key in the dict, and keys have to be unique,
we’re in trouble. Ergo, the name can’t be the key—something unique must be.
One option there is to use a UUID9 as the key, and map that UUID to names somehow. Maybe you have
another dict that, for a given name, stores a list of UUIDs that represent people who have that name. Then
we could ask the user, “Did you mean the Beej Jorgensen who was born in 1971, 1982, 1997, or 2003?” if
there were multiple Beej Jorgensens. (You can create a random UUID by importing the uuid package and
running uuid.uuid4().)
Lots of options for improvement, here!
(Solution10 .)
9.13 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. For the following dictionary, print out all the keys of the dictionary:
d = {
"key1": "value1",
"key2": "value1",
"key3": "value1",
"key4": "value1",
}
(Solution11 .)
2. For the dictionary in problem 1, print out all the keys and values.
(Solution12 .)
8
It’s not unusual.
9
https://en.wikipedia.org/wiki/Universally_unique_identifier
10
https://beej.us/guide/bgpython/source/examples/familytree.py
11
https://beej.us/guide/bgpython/source/examples/ex_printkeys.py
12
https://beej.us/guide/bgpython/source/examples/ex_printkeysvals.py
9.14. Summary 93
3. Given a list of names, write code that converts that list of names into a dictionary that groups names
by their first letter. In the dict, the key should be the first letter of the names, and the value should be
a list of names that start with that letter.
For example, the list:
["Chris", "Annie", "Beej", "Aaron", "Charlie"]
I don’t want you to manually convert the list to a dictionary; I want you to write code that does it for
any list of names.
After you construct the dictionary, print out the keys and values in any order.
(Solution13 .)
4. Change step 5 to print out the keys in sorted order. And the lists in sorted order after that.
(Solution14 .)
9.14 Summary
That was a heckuva chapter, wasn’t it?
When we learned about lists, we learned that you could index data by number. But now with dicts, we can
index data by any constant data type at all, including numbers and strings.
This gives us more flexibility in how we store data and how we look it up.
We learned about accessing and setting elements in a dictionary, how to determine if a key is in a dictionary,
and how to iterate over dictionaries with for.
Not only that, but dicts have a bunch of built-in functionality you can reference to manipulate and access the
data stored within them.
Finally, we saw that since dictionary values can be any type of data, you can have dictionaries of dictionaries,
even! The only limit is your imagination.
What other kinds of data can you store in dictionaries?
13
https://beej.us/guide/bgpython/source/examples/ex_list2dict.py
14
https://beej.us/guide/bgpython/source/examples/ex_list2dictsort.py
94 Chapter 9. Dictionaries
Chapter 10
Functions
10.1 Objective
• Understand what functions are and how they’re useful
• Be able to use built-in functions
• Understand what function arguments are
• Be able to write your own functions
• Be able to write good functions
• Understand the difference between positional arguments and keyword arguments
Then print out a grid of the distances between them. The grid’s top row and left column should indicate the
ship number (starting with 0).
Crossing a column with a row should give you the distance between the ships.
Distances should be printed to 2 decimal places in fields of width 8.
We’ll use a variant of the Pythagorean Theorem1 to find the distance between two 3D points.
95
96 Chapter 10. Functions
0 1 2 3
0 0.00 33.84 12.21 70.18
1 33.84 0.00 26.42 92.74
2 12.21 26.42 0.00 70.53
3 70.18 92.74 70.53 0.00
So we can see ship #2 (along the top) is distance 26.42 from ship #1 (along the left). And notice the diagonal
is all 0.00, which makes sense because every ship is zero distance from itself.
Keep this project in mind as we go through the chapter.
and have print() do all the dirty work of getting us the answer printed to the screen.
We’ve also used the input() function to get a string entered from the keyboard.
name = input("Enter your name: ")
This turns out to be a great way to simplify and organize code. Can you imagine if you had to put all the code
in to print out something on the screen every time you wanted to print something? Much easier to define the
print() function once, and then use it over and over again by calling it.
We have an important principle in computer programming called the DRY principle3 (Don’t Repeat Yourself ).
If you can remove as much repetitive code as you can and move it to a function, that makes your code easier
to read and maintain. DRY code is happy code.
Not only can we use functions to make DRY code, we can also use them to organize our code into logical
sections, even if a function is called only one time.
It is clearer to have your functionality in discrete sections that you call in sequence rather than just having a
single huge block of code that does everything
Python has a lot of built-in functions that you can use. It’s not necessary to memorize the usage in detail (you
can always look up the details), but it’s a good idea to skim their descriptions just so you can recall that they
exist.
We’ve already used print() and input() quite a bit, but there are plenty more. Look them up online4 .
Here are some common ones, though this is not an exhaustive list:
These are all available for use at any time in your program.
10.5 Arguments
“Oh look, this isn’t an argument.”
“Yes it is.”
4
https://docs.python.org/3/library/functions.html
98 Chapter 10. Functions
The arguments are the way you can pass data into a function to have it operate on that data to produce a result.
We refer to them by number, as well. “Pass the number of goats in as the second argument to the function.”
Functions often take a specific number of arguments. But, as we see above with print(), they can take
variable numbers of arguments, too.
Great! Oh, and we also need it for 8, 19, 21, 37, 402, 516, 1024, and 3490.
OK. Still no problem.
print((30 + 13) / 7)
print((8 + 13) / 7)
print((19 + 13) / 7)
print((21 + 13) / 7)
print((37 + 13) / 7)
print((402 + 13) / 7)
print((516 + 13) / 7)
print((1024 + 13) / 7)
print((3490 + 13) / 7)
Great! Oh, did I say “divide by 7”? I’m sorry, that should be “multiply by 7”.
5
https://www.defcon.org/html/defcon-2/defcon-2.html
10.6. Writing Your Own Functions 99
And here we get a taste of why DRY code is good code. Since the spec changed6 , we have to go back and
modify all those lines of code to make them right.
If only we hadn’t repeated ourselves, we might have been able to only change it in one place. This would
also be better because we wouldn’t be taking the risk of missing a place we should have changed the code.
But how to do it?
With… FUNCTIONS! Let’s write a function to do that math and return the result. Then we can just call it
over and over, like this:
print(do_the_math(30))
print(do_the_math(8))
print(do_the_math(19))
print(do_the_math(21))
print(do_the_math(37))
print(do_the_math(402))
print(do_the_math(516))
print(do_the_math(1024))
print(do_the_math(3490))
Sure, we’re repeating the function name do_the_math(), but we’re not repeating the actual mathematical
expression, itself, which is what matters.
But how do we define the function do_the_math()?
We use the def statement, like this:
1 def do_the_math(x):
2 result = (x + 13) * 7
3
4 return result
5
6 answer = do_the_math(30)
7 print(answer)
The indented stuff after the def is called the function body. That’s where all the work gets done.
There’s a lot of stuff to unpack here, so let’s take it nice and slow.
A parameter is a special type of local variable, one that comes pre-initialized with whatever value we passed
in as an argument. We’ll get into their story on line 2.
4 print(do_the_math(30))
Compare the two until you are convinced they are equivalent.
def timesdiv10(x):
return x * 10, x / 10
10.8. What Makes a Good Function 101
a, b = timesdiv10(100)
print(a, b) # 1000 10
Or you can assign the result to a single variable. The result will be a tuple, which is a read-only list that you
can access with square bracket notation:
a = timesdiv10(100)
Magic!
And then you go in and fill in all the code for processing the data and outputting the data.
But that could make for a bulky, hard-to-read while loop. It might be better to code it up like this:
while d in data:
d = process(d)
output(d)
and the write the functions process() and output() to operate on the data that is passed into them.
This makes the logic of the loop really easy to read. And being easy-to-read is king (or queen, if you prefer).
If you find you have some large amounts of code that are getting deeply-nested, it might be time to break
them out into functions, even if you only call those functions from that single place.
Knowing when to break up code into functions is more of an art than a science. If you start feeling like your
code is remotely unwieldy, consider what it might look like split into different functions. If you like it more,
do it!
We’ll talk more about this in detail later, but function arguments can be split into these two broad classes in
Python: positional and keyword.
Positional arguments are the arguments that have to occur at certain positions in a function call.
For instance, if we want to compute 1412 , we need to pass those two arguments in a specific order to the
math.pow() function, which expects the base to be first and the exponent to be second:
math.pow(14, 12) # 56693912375296.0
If you put the 12 first, you get a different answer. These are positional arguments.
But for some functions, after the positional arguments, you can specify additional keyword arguments. These
are arguments that are identified by a certain name.
For example, normally print() puts a newline at the end of the string. But you can override this behavior
with the end keyword argument by telling print() to put nothing (an empty string) at the end of the line:
# Together, prints "Beej" on a single line
print("Be", end="")
print("ej")
Ignoring the *objects for now, look at all those keyword arguments… in the docs, they have = followed
by some value. This indicates the default value if you don’t specify one otherwise. In other words, they’re
optional keyword arguments.
You can see that print() ends each line with a newline \n unless we tell it to end the line with some other
string.
For now, it’s enough to know that these exist and how to call them. Later on, we’ll talk about how to write
our own.
7
https://en.wikipedia.org/wiki/Evaluation_strategy
10.10. Interlude: Evaluation Strategies 103
# Assign a new list into x. The caller will _not_ see this change.
x = [4, 5, 6]
a = [1, 2, 3]
foo(a)
print(a) # [1, 99, 3]
• Call by Value: a copy of the argument is made and stored in the parameter. The C programming
language8 , among others, uses this one. (Sometimes people say Python uses Call by Value, but this is
technically not accurate since parameters are not copied when they are passed in; only the reference
to the argument is copied. You can make Python simulate Call by Value by making a copy of the
parameter when calling the function.)
# Simulating call-by-value in Python
def foo(x):
# Modify the passed-in object. The caller would normally see this
# change, but if they called it with a copy, only the copy will
# be affected.
x[1] = 99
a = [1, 2, 3]
print(a) # [1, 2, 3]
– Call by Reference: the parameter effectively becomes an alias for the variable name passed in
as the argument. Anything you do to the parameter you effectively do to the argument, including
assigning it some other value. Example languages Fortran9 , C++10 (usually by value, but can be
made by reference). (Sometimes people say Python uses Call by Reference, but this is techni-
cally not accurate since you can’t assign new values to parameters and see that reflected in the
argument.)
Here’s a quick summary between them.
Note: In the following table, “Can modify item in caller” means that if you have a variable in the caller that
refers to a thing, changes to the thing in the function will be reflected in the caller’s variable.
“Parameter reassignment affects caller” means that if you assign into a parameter variable (i.e. change the
parameter to refer to a completely different thing) the argument variable will also change.
Now, I’ve seen people be fast and loose with these descriptions when it comes to Python, so don’t hold them
to it. In my opinion, from the above three items, Call by Sharing is the most accurate. Call by Reference is
8
https://en.wikipedia.org/wiki/C_(programming_language)
9
https://en.wikipedia.org/wiki/Fortran
10
https://en.wikipedia.org/wiki/C%2B%2B
104 Chapter 10. Functions
the second-most.
We’re taking a _ top-down_ approach, here. Starting with the big pieces of logic and then implementing
them as we go down.
In fact, let’s simplify the problem, and just start like this:
locations = get_ship_locations()
print(locations)
#print_grid(locations)
That way we can just do one bit and make sure it’s working.
Fun Motto Fact: Get something working as quickly as possible, no matter how much a piece
of the project it is.
Problem-solving step: Understanding the Problem
For getting the ship locations, looks like we want to have the user enter an X, Y, Z coordinate. Then we want
to split that into a list of numbers. And we need to make sure they are int type—remember that input()
returns a string, so we’ll have to convert them to int.
And we repeat that until the user enters “done”.
Problem-solving step: Devising a Plan
Looping until the user enters “done” is a solved problem. We’ve done that a few times now. Just make a
variable for the while() condition, and then set it when the user enters “done”.
Otherwise, we have to split the string up into a list of 3 numbers, splitting on the comma. Remember how to
do that?
10.11. The Chapter Project 105
Yes, that’s right (or no, not right, if you don’t remember), it’s the split() string method. Example:
"1,2,3".split(',') # produces list ["1","2","3"]
But that’s not enough. As we noted, we need to convert that list from a list of strings to a list of numbers.
Remember how to convert a string to a number? Yes, with the built-in int() function!
int("2") == 2 # True!
So we can loop through our list of strings and convert them to ints.
And then we’ll have a list of 3 ints, representing the X, Y, Z coordinates of a single ship. Of course, the
user is going to enter any number of ships, and we’ll have to keep all those lists of coordinates somewhere…
where?
It’s almost like we’ll need a list for all those lists. A list of lists! Why not? We made some of those in the
lists chapter, right?
So we add the new X, Y, Z list to the end of a master list that holds all the coordinates.
Then we can return that master list to be used later when we print the grid.
Let’s code!
Wait—you’re right—we haven’t finished the entire plan for the whole project. True. But that’s okay. We
completed enough devising of a plan to do the first part of the project.
And this is one of the beautiful things about functions. We’ve split the problem up so nicely that we can im-
plement different parts of it completely independently! In fact, if you have a programming pal, you could get
them to write the print_grid() function at the same time you were writing the get_ship_locations()
function!
Problem-solving step: Carrying Out the Plan
Let’s get coding!
def get_ship_locations():
"""
Ask the user to enter a number of X, Y, Z ships coordinates.
Returns a list of all the coordinates.
"""
if xyz == "done":
done = True
else:
# Get a list of the x,y,z coordinates
xyz_list = xyz.split(',')
# Convert to integers
for i, v in enumerate(xyz_list):
xyz_list[i] = int(v)
return locations
locations = get_ship_locations()
print(locations)
#print_grid(locations)
There’s our list of lists holding the ships’ coordinates! It’s ready to feed into the print_grid() function.
But first, we’d better think about that for a bit.
One more thing: if you were looking closely, you saw the big multiline string at the beginning of the function
describing what the function does. This is called a doc string and it’s a comment that gives overall information
about the function. Automatic documentation generators can extract these and build documentation for you,
just like you can get with the help() function in the REPL.
Problem-solving step: Understanding the Problem
All righty. What do we need to do for this second part of printing out the grid of distances between the ships?
There are sort of three big pieces here.
• We need to print out a grid.
• We need the first row and first column to list out ship numbers so we can cross-reference.
• We need to compute the distance and print that.
Problem-solving step: Devising a Plan
Let’s simplify a bit first. Instead of worrying about computing the distance, let’s just concentrate on the grid.
We’ll just put the bogus number of 99.99 in for all the inter-ship distances.
Protip: When putting in bogus data, make sure it’s obviously bogus so that obviously people
obviously know they must obviously replace this with real data before the product ships.
And to simplify even further, let’s forget about the ship numbers in the first row and first column. We can
add those in later. Remember: it’s good to identify the minimum independent piece you can implement next
and test that it’s working.
We know how many ships we have—it’s the length of the master list of ship coordinates.
If we have n ships, we’ll need and n by n grid to be displayed to show all the distances between all of them.
But how? Think back to the loops chapter…
You can do it with a nested loop. The outer one goes for n rows, and the inner one goes for n columns.
For printing the number with 2 decimal places in a field of width 8, you can use a f-string with some special
formatting specifiers, for example:
distance = 99.99
print(f'{distance:8.2f}) # prints " 99.99"
num_ships = len(locations)
for i in range(num_ships):
for j in range(num_ships):
dist = 99.99
print(f'{dist:8.2f}', end="")
print()
locations = get_ship_locations()
print_grid(locations)
We printed it in field width 8 just for consistency with the distance numbers. Output is now:
0 99.99 99.99 99.99
1 99.99 99.99 99.99
2 99.99 99.99 99.99
Which is good! Now we just need a row on the top. How about another loop before everything else to print
out that row?
# Add this loop:
for i in range(num_ships):
print(f'{i:8}', end="")
print()
for i in range(num_ships):
print(f'{i:8}', end="")
for j in range(num_ships):
dist = 99.99
print(f'{dist:8.2f}', end="")
print()
Er, well, that’s almost right. The top row is shifted one column left. We need to stick 8 blank spaces in before
it to scooch it over. So let’s just do it, using string multiplication to make us 8 spaces:
print(" " * 8, end="") # <-- Add this
for i in range(num_ships):
print(f'{i:8}', end="")
print()
which is looking right on. Except that all the distances are listed as 99.99. Let’s get that out of there and
replace it with the real distance between the ships.
Problem-solving step: Devising a Plan
Distance! We see the distance formula up there at the top, repeated here:
𝑑 = √(𝑥0 − 𝑥1 )2 + (𝑦0 − 𝑦1 )2 + (𝑧0 − 𝑧1 )2
How do we turn that into code?
We could use math.sqrt() to get the square root, and math.pow() to square the numbers.
Another option is to compute, say, the difference in the X coordinates and then multiply that result by itself
to square it (since 𝑥 × 𝑥 =x^2$).
Let’s code them both and then use the one you like best.
Problem-solving step: Carrying Out the Plan
Here’s computing the distance between points p0 and p1 (both of which are lists of X,Y,Z triples) using
math.pow():
d = math.sqrt(
math.pow(p0[0] - p1[0], 2) + # difference in Xs
math.pow(p0[1] - p1[1], 2) + # difference in Ys
math.pow(p0[2] - p1[2], 2) # difference in Zs
)
And then we can mod our code where we hardcoded that 99.99 and have it call the distance formula instead!
We just need to pass in the two ships’ locations—one is represented by the column number, and the other by
the row number:
for i in range(num_ships):
print(f'{i:8}', end="")
for j in range(num_ships):
dist = dist3d(locations[i], locations[j]]) # <-- Mod here
print(f'{dist:8.2f}', end="")
print()
10.12 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. Write a program to input numbers repeatedly until the user types “done”. Keep track of all the numbers
in a list.
Print out the maximum value the user entered using built-in functions.
(Solution12 .)
2. Write a function that takes an integer between 0 and 9 as an argument. It should return a string that
corresponds to the English word for that number. For example, if the argument is 3, the function should
return "three".
(Solution13 .)
3. Write a function that accepts the mass of two planets and the distance between them (for a total of 3
arguments) and returns the force between them.
Use Newton’s Universal Law of Gravitation to calculate the force 𝐹 :
𝑚1 𝑚2
𝐹 =𝐺
𝑟2
In mathematical notation, to variables next to each other like 𝑚1 𝑚2 , above, indicate multiplication.
11
https://beej.us/guide/bgpython/source/examples/shipdist.py
12
https://beej.us/guide/bgpython/source/examples/ex_max.py
13
https://beej.us/guide/bgpython/source/examples/ex_ennum.py
110 Chapter 10. Functions
where m1 is the mass of one planet, m2 is the mass of the other planet and r is the distance between
them.
So far so good.
But what’s G? It’s the gravitational constant:
𝐺 = 6.67430 × 10−11
That’s written in scientific notation, but we can do the same thing in Python:
G = 6.67430e-11
That should be enough to go on. Write the function and call it with a variety of different values. Here
are some sample values so you can see if your code is working:
m1 m2 r result
10 20 30 1.48317e-11
10 40 30 2.96635e-11
100 5 10 3.33714e-10
(Solution14 .)
10.13 Summary
Functions are a super-important part of programming and a highly valuable tool to have at your disposal.
You can use them to break up code into smaller, more manageable pieces. You can also use them to create
standalone, reusable pieces of code.
Python comes with a pile of built-in functions, and it pays to know what they are (so you don’t reinvent them
yourself!)
Finally, you also learned that function arguments come in two flavors: positional and keyword. We’ll revisit
more of that topic later.
But now: well-deserved break time!
14
https://beej.us/guide/bgpython/source/examples/ex_grav.py
Chapter 11
11.1 Objective
• Learn what classes and objects are useful for
• Understand the relationship between classes and objects
• Be able to declare a class
• Be able to use that class to instantiate objects
• Understand that objects are mutable
• Understand the relationship between objects and None
• How to test to see if an object has an attribute or not
111
112 Chapter 11. Classes and Objects
Note that multiple theaters might be showing the same movie title. Avoid duplicating the movie data as much
as possible. (Remember: Don’t Repeat Yourself!)
Stretch goal: also store the per-theater showtimes for each movie at that theater.
These two pieces of information are clearly related. They apply to one single instance of a starship.
If we have multiple starships in the universe, they’ll each have their own names and coordinates.
So far so good?
Now… how do we store that data with what we know so far?
Well, we have lists, so let’s try with those. We’ll have three starships with different names and different
locations:
ship_location = [
[10, 20, 30],
[-10, 20, -30],
[10, -20, 30]
]
ship_name = [
"MCRN Tachi",
"Red Dwarf",
"USCSS Nostromo"
]
So we have two lists, one for name and one for location1 .
It works, but it’s a bummer to have to maintain two (or more) lists this way. If we ever added a ship, we have
to be sure we add all the information to all the lists and make sure things don’t get out of order. It’s easy to
make a mistake and get the lists out of sync.
1
This technique is called parallel arrays or, in the case of Python, parallel lists. It’s not a popular technique today, having been
made obsolete by the very thing we’re talking about in this chapter.
11.4. What are Classes and Objects? 113
What would be nice is if we could bundle all the information about one single ship into one single object that
held the information about just that one ship. Other ships would be represented by other objects. And then
we’d have a list of those objects—just one list to maintain!
s0 = StarShip()
s1 = StarShip()
s2 = StarShip()
By putting parens after the class name, we’re telling Python that we are creating a new starship object from
the StarShip class. In fact, we made three of them and saved a reference to each in s0, s1, and s2. Of
course, none of them have names or locations, but we’ll remedy that shortly.
If we print one, we get something like this:
2
https://en.wikipedia.org/wiki/Camel_case
114 Chapter 11. Classes and Objects
s0 = StarShip("MCRN Tachi")
s1 = StarShip("Red Dwarf")
s2 = StarShip("USCSS Nostromo")
What’s weird is that when we instantiated the starships, we only passed one argument to __init()__.
s0 = StarShip("MCRN Tachi")
When clearly __init__() has two parameters: self and name. What gives?
def __init__(self, shipname): # The constructor
Python then takes the arguments we did pass and tacks them on after that.
Let’s take a look at that constructor again:
class StarShip:
def __init__(self, shipname): # The constructor
self.name = shipname
s0 = StarShip("MCRN Tachi")
This is a really common pattern, so let’s make sure we understand what’s going on here. In particular, there’s
a weird dot after self. What does that mean? But before we get there, let’s look at shipname.
When we create our new starship s0, we pass in the name "MCRN Tachi". This calls the constructor
__init__().
Python automatically puts a reference to the object that’s now being constructed into self. And then it copies
the string "MCRN Tachi" to the shipname parameter of the function. So shipname is "MCRN Tachi".
And now we’re to the guts of the thing. self.name? The saga continues!
11.6 Attributes
Problem-solving step: Understanding the Problem.
Objects have variables attached to them, and we call these attributes5 .
Attributes are qualities that an object possesses—what things it has. For example, a starship would possess
a name. In other words, a starship would have a name attribute.
And we refer to these attribute by using the dot operator (.).
When we have a line like this:
self.name = shipname
We’re saying “change the name attribute on this starship object to be the same as the shipname parameter
that was passed into this method”.
This is how we take the ship name that was passed in as an argument and attach it to the newly constructed
object! We save a reference to the name in an attribute on the object!
Note that I named shipname differently than ship deliberately. (I did this to show that they could be different,
but also to avoid confusion when looking at the example.) But it’s far more common to just name them the
same thing. This is OK since self.name, the attribute on self, is different than name, the parameter.
Like so:
class StarShip:
def __init__(self, name): # The constructor
self.name = name
class StarShip:
def __init__(self, name): # The constructor
self.name = name
self.location = [0, 0, 0] # <-- Add this
All ships will now start at [0,0,0] because that’s what the blueprint says they’ll do.
And we can print it! We’ll access the values in those attributes by using the dot operator on s0!
s0 = StarShip("MCRN Tachi")
s0.name = "Rocinante"
print(s0.name) # Rocinante
And we could modify the location of the ship this way, as well:
s0.location[1] = 99
This is important! Even though we’re changing the values for the attributes of s0, the attributes of the other
objects (like s1 and s2) remain unchanged! (Until we explicitly change them.)
We’ve successfully bundled all the information about a single ship into this single object. Nice and consoli-
dated.
9 self.location[0] = x
10 self.location[1] = y
11 self.location[2] = z
12
13 s0 = StarShip("MCRN Tachi")
11.9. Pretty Printing 117
14
15 print(s0.location) # [0, 0, 0]
16
On line 6, we define our new method, set_location(). Importantly, notice the first parameter is self,
which will be initialized to represent the object we’re setting the location of. (That is, when we call
s0.set_location(), self will be set to refer to s0 inside set_location().)
Fun Debugging Fact: If you get an error about the incorrect number of arguments to your
method, make sure you have self as the first parameter!
Then on line 17, when we call set_location() on s0, self gets set to s0, and x, y, and z get set to 10, 20,
and 30, respectively.
Then we use x, y, z, and self inside the method to change the values in this ship’s location.
This way, when we print it out on line 19, we see the new values there.
Attributes!
Not particularly useful. Let’s override that functionality and have it print something nicer.
Go ahead and add this method to the StarShip class:
def __str__(self):
"""Return string representation of this object."""
return f'{self.name}: {self.location}'
The __str__() method returns a string to use any time an object is printed.
Now if we build three new ships and print them all:
s0 = StarShip("Rocinante")
s1 = StarShip("Red Dwarf")
s2 = StarShip("USCSS Nostromo")
print(s0)
print(s1)
print(s2)
Perfect!
118 Chapter 11. Classes and Objects
x.antelopes = 4
This means you can pass objects to functions as arguments, and the function can change the values in the
object’s attributes.
def set_antelopes_to_10(o):
o.antelopes = 10
class Forest:
pass
x = Forest()
x.antelopes = 4
set_antelopes_to_10(x)
print(x.antelopes) # 10!
Remember that when you call a function, it is assigning the argument into the parameter name, so both of
them refer to the same object. That is, in the code above, o refers to the same object x does, just as if we’d
done an assignment with o = x.
5 def __str__(self):
6 return self.name
7
11.12. Testing for Attributes 119
18 person_list = [
19 Person("Annie"),
20 Person("Beej"),
21 Person("Chris")
22 ]
23
24 p = get_person_by_name(person_list, "Chris")
25
26 print(p) # "Chris"
27
28 p = get_person_by_name(person_list, "Dave")
29
30 if p is None:
31 print("Dave's not here.")
f = Foo()
f.bar = 12
print(getattr(f, "bar")) # 12
print(getattr(f, "frotz", None)) # None, since attr frotz doesn't exist
print(f.frotz) # 99
I wouldn’t say that these functions get a lot of day-to-day use, but they’re a powerful thing to add to your
toolkit.
So far so good.
Now we need to instantiate a bunch of movies so that we can add them to the theaters’ .movie lists.
There are a couple of things we could do.
We could use one variable per movie, but that’s a bit unwieldy. Let’s use some kind of collection, like a list!
We’ll make one for all the movies and all the theaters. Go ahead and add your favorites.
11.13. Chapter Project 121
14 movies = [
15 Movie("Star Wars", 125, "scifi"),
16 Movie("Shaun of the Dead", 100, "romzomcom"),
17 Movie("Citizen Kane", 119, "drama")
18 ]
19
20 theaters = [
21 Theater("McMenamin's Old St. Francis Theater"),
22 Theater("Tin Pan Theater"),
23 Theater("Tower Theater")
24 ]
25
Take a look in there to see what we’ve done. Notice that movies is a list, and inside the list, while we’re
initializing it, we’re constructing new Movie objects.
And we do the same thing with theaters. It’s a list of newly-constructed Theater objects.
Nextly, we need to associate those movies with the theaters that are showing them.
Remember that each Theater object has a list of movies in its .movies attribute. So we need to append the
movies to that list.
This next bit is a little cryptic, so make sure
26 # McMenamin's is showing Star Wars and Shaun of the Dead
27 theaters[0].movies.append(movies[0])
28 theaters[0].movies.append(movies[1])
29
37 theaters[2].movies.append(movies[2])
38
What we’ve done here, effectively, is linked up all the movie objects with their respective theaters.
Notice how movies are listed in multiple theaters. For example movies[0] (Star Wars) is in theaters[0]
(McMenamin’s) and also in theaters[2] (Tower).
Does that mean there are two copies of the Star Wars Movie object? Since it’s in two theaters?
Think carefully!
No, there’s just one! The one we created back on line 15! Since it’s an object, making a “copy” through
assignment (or with .append()) just makes another reference to the same object. There’s only one, but it’s
referred to by two Theater objects. And also referred to by the movies list. So many references to the same
object for good memory savings.
Now we want to print out all the theaters and their showtimes. I’m going to make a helper function here to
print a single theater’s data. We’ll pass in a reference to a theater object, print its name, and then print the
data for all the movies in its .movies list.
39 def print_theater(theater):
40 """Print all the information about a theater."""
41
42 print(f'{theater.name} is showing:')
43
44 for m in theater.movies:
45 print(f' {m.name} ({m.genre}, {m.duration} minutes)')
46
And lastly, all we have to do is call print_theater() for all the theaters in our theaters list:
47 # Main code
48 for t in theaters:
49 print_theater(t)
It might be convenient to have some kind of helper function that could lookup the movie object by name,
similar to this:
def find_movie_by_name(movies, name):
for m in movies:
if m.name == name:
return m
6
https://beej.us/guide/bgpython/source/examples/moviesign.py
11.13. Chapter Project 123
and use that to clean up the code a bit. And something similar for theaters. (Of course, the more movies you
have, the longer it takes to find one. A dictionary might be a faster data structure to use here.)
But this approach doesn’t handle the case where there are two movies or theaters of the same name. So
another workaround would have to be found there—may be a unique identifier number for each theater and
movie that we’d key off instead?
Now… what about that stretch goal to add movie times to all this?
Problem-solving step: Understanding the Problem.
This one might not seem tricky at first, but it comes to get you with the details.
You might think, no problem, we’ll just add times to the Movie class, right?
Yes, but… Different theaters are all showing the same movie. But at different times.
If you think about it, the times a movie is showing is more data attached to the theater, and not really data
attached to the movie. It would make no sense for Disney to say, “Coming this Winter: Star Wars Episode
47, at 8 pm and 10 pm!” They don’t know when theaters are going to show the movie!
Okay, then, let’s attach the times to the Theater class.
But this presents us with another problem. How do we associate a set of times with a particular Movie object?
We need a way in code to show that they’re linked so that we can print them out together.
Problem-solving step: Devising a Plan
We can do this with a new class—call it MovieTime—that contains both a reference to a movie and a list of
times that movie is showing. And then we can add instances of this new class to the Theater objects.
In this way, if we have a reference to a Theater, we can look up its list of MovieTime objects, and then for
each of those, look up the Movie object reference contained within and print it out along with the times.
We’re shimming a new class in the middle with both the movie and the showtimes. This is how we can
bundle that together.
Problem-solving step: Carrying Out the Plan
Let’s add that new class that holds both a reference to a movie as well as the times it’s showing:
class MovieTime:
"""Holds a movie and the times it is playing"""
def __init__(self, movie, times):
self.movie = movie
self.times = times
Then we need to modify the Theater class to have a list of MovieTime objects instead of Movie objects.
class Theater:
"""Holds all the information about a specific theater."""
def __init__(self, name):
self.name = name
self.movietimes = [] # <-- Now this is MovieTime objects
And now when we construct our lists of theater information, we need to add new MovieTime objects to the
list in the theater. The MovieTime objects contain references to the movie being shown, as well as a list of
showtimes.
# McMenamin's is showing Star Wars and Shaun of the Dead
theaters[0].movietimes.append(MovieTime(movies[0], ["7pm", "9pm", "10pm"]))
124 Chapter 11. Classes and Objects
Lastly, when we print it out, we need to extract the movie and the show times from the MovieTime object so
we can print them:
def print_theater(theater):
"""Print all the information about a theater."""
print(f'{theater.name} is showing:')
for mt in theater.movietimes:
m = mt.movie
t = " ".join(mt.times) # Make string of times separated by spaces
print(f' {m.name} ({m.genre}, {m.duration} minutes): {t}')
(Solution7 .)
Aside from the improvements noted in the last “Looking Back”, we might be able to fix this one up a bit with
respect to how it handles times.
Right now, we’re storing the times in strings, but it would be better to store them as datetime objects from
the Python standard library8 .
This would enable us to do date math with the showtimes, e.g. to tell the user how many minutes until the
next showing.
7
https://beej.us/guide/bgpython/source/examples/moviesign2.py
8
https://docs.python.org/3/library/datetime.html
11.14. Exercises 125
11.14 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. Write a class that describes a car. What are the attributes the class would have? What methods?
(There’s no one right answer here—think freely.)
(Potential Solution9 .)
2. Write a class called SubwayCar that represents a single train car on a subway train. What attributes
would it have? What methods?
Add a name attribute to the class so you can name the cars.
Add a next attribute to the class that points to the next SubwayCar in the train. This should refer to
the next SubwayCar instance, or to None if it’s the last car.
Have a variable, head, that points at the first subway car.
This way you can “hook together” a train, like this (pseudocode):
head = SubwayCar("Engine")
car1 = SubwayCar("Passenger car 1")
car2 = SubwayCar("Passenger car 2")
car3 = SubwayCar("Passenger car 3")
head.next = car1
car1.next = car2
car2.next = car3
car3.next = None # End of the train
Now have a variable, location, that is your current location in the train. Start it at the head:
location = head
Then write a loop to “walk” the location variable down the train (by following the next pointers),
printing out the name of each car as it goes, until it reaches the end.
This famous data structure is actually called a linked list. But I disguised it as a subway train to be less
intimidating.
(Solution10 .)
3. Make a Room class that has a name attribute.
Also give it n_to, s_to, w_to, and e_to attributes. These will refer to the rooms that are north,
south, west, and east of a particular room. None in one of these attributes means there’s no exit in that
direction.
For example, two rooms that are hooked up west to east (and vice versa) could be constructed like this:
room0 = Room("Cobble Crawl")
room1 = Room("Debris Room")
9
https://beej.us/guide/bgpython/source/examples/ex_car.py
10
https://beej.us/guide/bgpython/source/examples/ex_subway.py
126 Chapter 11. Classes and Objects
Next, get player input of either n, s, w, or e, and change location to the room in the specified direction.
If there’s no room there, print the string "You can't go that way.".
If the user enters q, quit the game.
(Solution11 .)
11.15 Summary
All kinds of goodies in this chapter! We dipped our toes in the magical world of classes and objects, which
is the beginning of learning the world-famous Object-Oriented Programming (OOP).
We saw how we could concisely bundle data and functionality into a single convenient class and the make
objects from the class, using the class as a blueprint.
And, importantly, we learned that multiple variables can refer to the same object—that objects are not copied
when you make an assignment.
Finally, we touched on the idea that None could be used to indicate “absence of an object”.
Though objects and classes form the basis for OOP, we really haven’t touched on what that means yet. But
that’s a story for another chapter.
11
https://beej.us/guide/bgpython/source/examples/ex_adv2.py
Chapter 12
Importing Modules
12.1 Objective
• Learn what a module is
• How to find modules to use
• How to import modules
(Spacing in the above example was changed to fit the margins—you don’t have to match spacing exactly.)
Keep this project in mind as we go through this chapter’s material.
127
128 Chapter 12. Importing Modules
Do you want to compute anything to do with date and time? Python has a module for that.
Do you want to draw graphics on the screen? Python has a module for that.
Do you want to generate animated GIFs from a sequence of stills? There’s a module for that.
Do you want to download an image from a URL and save it to disk? There are modules for that.
There are tons of modules built-in to Python_2 . Give the list a skim so you know what’s there, but you don’t
need to drill down at all unless you’re dying of curiosity over a particular module.
Are there are zillions of third-party modules3 you can use, as well.
You can import as many modules as you want into an individual project.
You declare your intent to use a module with the import keyword. We’re going to do an example with the
built-in sys module4 , which contains all kinds of useful information about the system.
When using a function or variable inside a module, you use the dot operator to get the variable, similar to
how you get attributes from objects. In fact, very similar. When you import a module, Python gives you an
object by that name with functions and data attached to it as attributes.
Step one is to import the module. Then you can access the members of that module.
we’ll end up with an object called sys with attributes that you can access!
Digging through the documentation for the sys module, we find there’s something called sys.platform
that looks really promising.
print(sys.platform)
For me, on Linux or Windows WSL, it prints “linux”. On Windows, it prints “win32”.
Notice there was nothing we had to do ourselves to make this determination. (Which is good, because it
would be a pain to write!) Fortunately, the sys module had everything we needed right there.
2
https://docs.python.org/3/library/index.html
3
https://pypi.org/
4
https://docs.python.org/3/library/sys.html
12.5. Command Line Arguments 129
Those extra words after the program name are called command line arguments.
But why would you do this?
So you can control the behavior of the program from the command line! When you run it, it’s nice to be able
to influence behavior this way instead of having to call input() with prompts and everything else.
But how do you get those extra command-line arguments?
Our good friend sys module can help us again here.
The variable sys.argv is a list that contains the program name followed by all the command line arguments.
Run this program with a variety of command-line arguments and see what it outputs:
import sys
print(sys.argv)
Example output:
$ python foo.py
['foo.py']
$ python foo.py aa bb cc
['foo.py', 'aa', 'bb', 'cc']
So at runtime, we can look in sys.argv and make decisions about what we want to do!
Let’s put it to use in the next section.
Reading docs is one of the things you’ll get better at with practice. At first, it’s a bit of a slog, but you’ll
improve.
First of all, start skimming down and looking for anything that has to do with text calendars. If it doesn’t
seem to have anything to do with that, keep skimming.
I’ll wait. Go for it.
Spoilers coming! Really go scan them and find it yourself! Practice makes perfect!
Problem-solving step: Devising a Plan
OK—so hopefully you got about halfway down the page and found the TextCalendar class. It says:
This class can be used to generate plain text calendars.
That sounds promising. In fact, just below that, it mentions there’s a prmonth() method on the class that
you can use to print a calendar for a given month and year.
Perfect!
Problem-solving step: Carrying Out the Plan
We can code it up like this:
import calendar
and this will present us with a nice text calendar that looks like this:
January 1970
Mo Tu We Th Fr Sa Su
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
calendar.prmonth(1970, 1)
or
python cal.py 1955 11
year = sys.argv[1]
month = sys.argv[2]
calendar.prmonth(year, month)
Yikes!
Let’s take this error apart and see if we can tell what’s up. It’s not really being that forthcoming, is it?
Start at the top. It tells you what file the error is in on the first line: cal.py on line 7. And it shows us the
line below that… it’s where we’re calling calendar.prmonth().
But that looks fine, right?
Going farther down, it’s showing us the call stack, that is, the path of function calls that culminated in the
error. And those are in the calendar.py file, which is the calendar module.
We didn’t even write that code! How dare there be an error in it!
132 Chapter 12. Importing Modules
Well, it’s not an error—it’s the module telling us, in a roundabout way, we’re not using it right.
Finally, at the bottom, we see the error itself: TypeError. And the description:
TypeError: list indices must be integers or slices, not str
We don’t have any lists in our code, so what’s it even talking about lists for? Well, who knows how the stuff
is implemented in the library, but scan that error message and see if there’s anything in there that hints toward
what we have to do.
It says “must be integers or slices, not str”. Hmmm.
When we called it with
calendar.prmonth(1970, 1)
it was fine, but now it’s not? Wait—when we called it that way, we passed integers in… but now we’re
passing in sys.argv[1]. Is that an integer?
There’s a built-in function called type() we can use. Let’s add this code:
import sys
import calendar
year = sys.argv[1]
month = sys.argv[2]
calendar.prmonth(year, month)
Running it again, we get the same error, but before that, we see some output:
<class 'str'>
<class 'str'>
That’s telling us sys.argv[1] and sys.argv[2] are strings! And we were passing ints before. Let’s convert
those to ints before we pass them in. The error message did say we needed ints, not strings.
import sys
import calendar
year = int(sys.argv[1])
month = int(sys.argv[2])
calendar.prmonth(year, month)
Whee!
Problem-solving step: Looking Back.
12.7. Importing Specific Attributes 133
if len(sys.argv) != 3:
print("usage: cal.py year month")
sys.exit() # stop running
year = int(sys.argv[1])
month = int(sys.argv[2])
calendar.prmonth(year, month)
Ship it!
But if you’re going to call it repeatedly, it might make the code look worse to have “time.” all over the
place.
We can do this, instead:
# Bring in ctime() explicitly:
If a module has multiple things you want to import, you can bring them in with a comma list:
# import all three!
But I generally recommend against that. It takes time for Python to do it, and if you only need a few things,
pick them explicitly.
Furthermore, a lot of devs rely on the module name being a visual cue that we’re talking about a function in
a module, here. If we come across some code that says:
print(ctime())
Is that a function that the programmer-defined, or is it something that we from imported from somewhere?
By putting the name of the module first, it helps mitigate that ambiguity:
print(time.ctime())
So in general, I don’t use import from unless it makes the code decidedly more readable to do so.
Remember: readable code is high-value code!
And that’s the end of my skim. How did it compare with your list?
Now… Those last three look interesting. If we could get the ZipInfo object for each item in the archive, we
could use those attributes to print out our directory listing.
But that sounds like it’s just going to get us what the printdir() function would do, and printdir() looks
easier to use.
Maybe we’re wrong, but let’s pursue printdir(), and if it doesn’t pan out, we can go to Plan B and try the
ZipInfo fields.
Let’s go!
Check.
Okay—now we need to do something with that ZipFile constructor. Recall that since it’s in the zipfile
module, we have to refer to it as zipfile.ZipFile when we use it.
But, man, the docs are thick for the constructor. What is all that stuff?
Remember that any keyword argument with something after an equal sign is optional. We don’t have to pass
arguments for mode, compression, or any of those.
What we do have to pass in in the file, which is the filename to read. Let’s do that, and we’ll save the
newly-constructed object in the variable zf:
3 # Important: make sure example.zip is in the same directory
4 # as this program!
5
6 zf = zipfile.ZipFile('example.zip')
7
Great!
And now that we have that object, let’s print its directory:
8 zf.printdir()
Yes!
One easy thing to do would be to use sys.argv to get the name of the archive to print out the listing for.
Another thing that you might have noticed in the docs is there is all kinds of additional info about members
of the archive. In addition to name, time, and size, there’s also comments, compression type, compressed
size, CRC9 , and other things to print.
By adding up all the uncompressed sizes, the compressed sizes, and then dividing one by the other, you can
get the compression ratio—how much smaller the files got by putting them in the archive.
9
https://en.wikipedia.org/wiki/Cyclic_redundancy_check
10
https://beej.us/guide/bgpython/source/examples/zipdir.py
11
https://beej.us/guide/bgpython/source/examples/example.zip
138 Chapter 12. Importing Modules
12.10 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. Every process running on your system is represented by a numeric process ID. When you run a program,
it gets a unique process ID (PID) that exists until the process exits.
Write a program to print out its current process ID. Check out the docs for the os module12 for hints.
You might want to search that page for anything to do with “current process ID”… :)
Don’t forget to import the module!
When you run it, run it multiple times to see that the PID changes from run to run.
(Solution13 .)
2. Write a program to generate random UUIDs. A UUID (pronounced “YOU-id”) is a random string of
letters and digits that looks like this:
54c3bfab-fd9f-4f4a-96db-8f9fccff88cd
(Well, yours will be different because it’s very, very, very unlikely that you’ll ever generate the same
random one twice.)
It’s short for Universally Unique ID. That means it’s unique in the universe, forever. Very, very prob-
ably.
Using the UUID module14 , generate a random UUID.
Actually, generate several. Have the user enter a number on the command line. Generate that many
UUIDs.
For example:
$ python ex_uuidgen.py 5
8a8128fb-941a-4a2f-8982-75273d7c0048
5fd7d64e-8491-4b61-82b0-f9438e7195dc
4012f3ed-f6d7-40b5-9031-961ee06a30ad
86c71566-014f-4e36-a55b-b18d677624b2
2c1e5b3f-f0de-4186-80c5-767628c437b3
Eagle-eyed readers might notice that the 13th digit is always 4. That’s because there are different types
of UUIDs, and this digit indicates the type. (This case it’s type 4, meaning random. Except for the 4.)
You might also have noticed that, in addition to the numerals, only the letters “a” through “f” make
an appearance. Surprise! UUIDs, except for the hyphens, are actually numbers! They’re written in a
base-16 numbering system called hexadecimal. More on that in another chapter.
UUIDs are good any time you want to create an ID that you can be confident isn’t already used by
anyone, anywhere.
You might wonder how you can be sure? I mean, there’s a chance someone else will choose that
number, right?
Yes, there is a chance. It’s:
12
https://docs.python.org/3/library/os.html
13
https://beej.us/guide/bgpython/source/examples/ex_printpid.py
14
https://docs.python.org/3/library/uuid.html
12.10. Exercises 139
1 in 21,267,647,932,558,653,966,460,912,964,485,513,216.
For comparison, the odds of winning the Mega Millions lottery jackpot are:
1 in 258,900,000.
So unless you’re worried about winning the lottery jackpot 82,146,187,456,773,479,978,605,303,068
times, you shouldn’t be worried about someone choosing a duplicate UUID.
And I wouldn’t say I’m worried I’d win the lottery that many times. More like disappointed.
(Solution15 .)
3. You’re given the following string in Python—go ahead and paste it into a new source file:
matrix = """The Matrix is everywhere. It is all around us. Even
now, in this very room. You can see it when you look out your window,
or when you turn on your television. You can feel it when you go to
work, when you go to church, when you pay your taxes."""
There’s a handy module called textwrap16 that has some functionality that you can use to make your
life easier.
(Solution17 .)
4. Print a random integer between 0 and 1000, inclusive.
It should print a different number every run, for example:
$ python ex_rand1000.py
601
$ python ex_rand1000.py
374
$ python ex_rand1000.py
824
In case it happens to come up, locale refers to the human language spoken in the physical location
where the program is running, e.g. English or French or Chinese or Esperanto, etc.
(Solution21 .)
6. Write a program called zipextract.py that extracts files from a ZIP archive.
The command line should accept the name of the ZIP file as the first argument, and, optionally, the
name of the file in the archive to extract.
If the second argument is left off, extract all the files.
To extract all files:
python zipextract.py example.zip
(Solution22 .)
12.11 Summary
Modules make the world go around… a lot more easily than it would have if you had to write all that stuff
yourself.
In this chapter, we learned what modules were and how to find them in the official Python docs.
Also, we learned how to import entire modules and individual components from within modules.
Later we’ll learn to write and import our own modules.
21
https://beej.us/guide/bgpython/source/examples/ex_curdate.py
22
https://beej.us/guide/bgpython/source/examples/ex_zipextract.py
Chapter 13
Reading Files
13.1 Objective
• Understand what a file is
• Be able to open and close a file
• Be able to read and write from files
13.2 Project
Write a line editor.
Back in the day, before terminals were very capable and before we had nice editors and IDEs like we do
today, people used line editors. These were bare-bones file editors that used simple commands to edit files.
For example:
$ python lineedit.py foo.txt
> l 1
1: This is some text that was already in the file.
> a 1
This is some text that I'm appending to the file.
And some more.
.
> l 1
1: This is some text that was already in the file.
2: This is some text that I'm appending to the file.
3: And some more.
> e 3
And really some more.
> l 1
1: This is some text that was already in the file.
2: This is some text that I'm appending to the file.
3: And really some more.
> d 2
> l 1
1: This is some text that was already in the file.
2: And really some more.
> w
> q
141
142 Chapter 13. Reading Files
We’ve already seen a bunch of examples—our Python source files that we’ve been saving this whole time!
They’re sequences of characters stored in a specific order and saved on disk.
Now, files are actually far more general-purpose than that, but let’s start with this. We can always chase the
rabbit further down the rabbit hole later.
For this chapter, we’re going to use a type of file commonly called a text file. This is also just a sequence
of characters, just like a Python source file. But text files can be anything. Love letters, The Gettysburg
Address, the lyrics for the latest hit single by that one band, the number 𝜋 computed to a million decimal
places, or whatever.
Technically, Python source files are text files, as well, but they’re a specific type. All Python
source files are text files, but not all text files are Python source files.
As a human, you can identify the type of file by its extension. That is, the part of the filename after the last
period in the file.
For Python, we’ve been using .py (pronounced “dot-pie”) as the extension, identifying this file as a Python
source file.
General text files use .txt (“dot-text”) as an extension. And you can make them with the same editor you’ve
been using to write Python code.
Go ahead and open a new file, and call it wargames.txt. Enter some text into it, like this:
What he did was great! He designed his computer so that it could learn from its own mistakes.
So, they’d be better the next time they played. The system actually learned how to learn. It
could teach itself!
Now, the question is, how do you run a Python program that reads this file in, and stores or manipulates the
data in memory?
we’ll get the output in uppercase. (But the original file is still lowercase, of course!) It’s our copy of the data
to do with as we please!
3
https://beej.us/guide/bgpython/source/examples/fileread1.py
4
Meaning, the One Right-ish Way to do something.
13.6. Reading Data a Line at a Time 145
If you’re looking closely, you’ll notice that the f.close() is missing. That’s because when you use with
to open the file, that gets handled automatically for you! Not only that but even if some error occurs, the file
will be properly closed.
Although the pattern of open-read-close is really common in other languages and Python supports it, the
preferred way of doing things is with the fantastic with statement.
2: from its own mistakes. So, they'd be better the next time they played.
Pretty neat, eh? We just get to use a for loop on the opened file to read one line at a time.
But wait! Why is there an extra newline being printed out? What are those blank lines between the lines?
This is a common beginner mistake. The reason is that there is a newline at the end of every line of the file
already (because that’s where the line breaks are). And, in addition, print() adds its own newline! So we
get both of them printed.
The easiest workaround is to use the end keyword argument on print() to stop it from adding a newline of
its own:
print(f'{line_num}: {line}', end='')
Another option is to use .rstrip() on the string to strip newlines from the end.
line = line.rstrip('\r\n')
That’ll strip carriage returns or newlines from the right side of the string. We have to specify both since some
OSes use different characters to represent the end of the line, somewhat irksomely.
An even-more-portable way to write this is to first:
146 Chapter 13. Reading Files
import os
then
line = line.rstrip(os.linesep)
and Python will automatically use the proper end-of-line character no matter what system you’re running the
program on.
There we go! If you run this, then have a look, you’ll see a file called newfile.txt that has the magic words
in it.
Now you have the power to save data to disk and read it back again!
• Delete lines
So first, make sure we know what all those do.
Then we’ll attack.
Problem-solving step: Devising a Plan
We’re going to do something a little different this time. We’re going to make an overarching plan, and then
jump back and forth between Planning and Carrying Out the sub plans the larger plan is comprised of.
This is a little more like how software dev actually works. You have a general idea of where you’re going,
and you work out the details as you get to them.
This is a bit of a double-edged sword, and it takes practice and experience to get it right. You
don’t want to over-plan, because you’re undoubtedly going to have to change things and you
don’t want to waste your time. And you don’t want to under-plan, because then hidden gotchas
might… get you later. You want to plan just the right amount. Whatever that is.
In reality, devs consistently under-plan. And they still make it work somehow, like MAGIC.
And by “MAGIC”, I mean tons of sweat, tears, and off-color language.
Being able to do this is a really, really important skill to practice. This is where software development really
happens. The rest of coding is just writing things down.
So let’s do the rough overall, based on the outline, above in Understanding The Problem.
• First we’ll get the filename from the command line.
• Then we’ll read the file.
• Then we’ll loop to get input for whatever it is the user wants to do (append, list, edit, write, etc.)
• For whatever the user enters, we’ll do that thing.
• When the user says q to quit, we’ll quit.
That’s a pretty loose plan. I mean, “We’ll do that thing,” isn’t exactly well-fleshed-out. But we know it’s
possible to do them, and we can work on those individual command components one at a time (since none
of them are really dependent on one another).
Looks like some of those we can work on right away.
Problem-solving step: Devising a Plan
Let’s start simple. Simplifying the problem is always a good way to get bits and pieces done. Also, getting
a minimum working piece going as soon as possible can help direct our efforts and keep motivation up. Get
a core piece in place, and then keep adding on.
What’s a simple version of the program?
Well, we could start by looking at the command line to see if there’s a filename there or not, and storing it if
there is.
Remember the user has the option to run the program without specifying a filename (since maybe they’re
creating a new file).
So we want to check the command line args in sys.argv. If the user specified a filename, we’ll store it. If
they didn’t, we’ll store None to indicate that case. If they specify more than one argument, we’ll print out a
usage message.
Great! Let’s go!
Problem-solving step: Carrying Out the Plan
import sys
if len(sys.argv) == 2:
filename = sys.argv[1]
elif len(sys.argv) == 1:
# We'll use this as a sentinel value later if we need to prompt for
# a filename when writing the file.
filename = None
else:
print("usage: lineedit.py [filename]", file=sys.stderr)
sys.exit(1)
That last line is only there temporarily. This lets us test things out.
$ python lineedit.py
None
with open(filename) as f:
for line in f:
lines.append(line)
return lines
Now if we run that, and specify an input filename, it should print out a list with all the lines in that file.
Of course, we need a sample input file. You can make one in VS Code—just edit a new file called lines.txt
and put whatever you want in it. (About five lines is good for testing.) If you don’t want to bother, there’s a
file lines.txt in the examples directory5 .
Running it, we get our list, just like we wanted!
$ python lineedit.py lines.txt
['This is line 1\n', 'This is line 2\n', 'This is line 3\n',
'This is line 4\n', 'This is line 5']
$ python lineedit.py
[]
Take a moment to digest what we did there: we made a copy of the data that was on disk and stored that copy
in memory.
What’s next in the big overall plan? Looks like it might be time for a user-input loop.
Problem-solving step: Devising a Plan
This is like so many other input loops we’ve done so far:
• Print a prompt
• Parse the input
• Run the command
• Stop looping when the user says to quit
Problem-solving step: Carrying Out the Plan
Your standard input loop. All it does is let you type q:
# Main loop
done = False
5
https://beej.us/guide/bgpython/source/examples/lines.txt
150 Chapter 13. Reading Files
if command[0] == 'q':
done = True
If you hit RETURN it pukes right away because command[0] isn’t a thing if the string is empty.
A common thing to do here is just print another prompt if the user enters a blank line. We can add this after
the input() line to do that:
# If the user entered a blank line, just give them another prompt
if command == '':
continue
And finally, let’s add some output as an else to tell the user if they input something we didn’t recognize:
else:
print("unknown command")
OK! Now if we run it, we should be able to handle blank lines, unknown commands, and q for quit.
$ python lineedit.py lines.txt
> x
unknown command
> [ user hit RETURN a few times here ]
>
>
> q
$
Not much of an editor so far, but Facebook wasn’t built in a day. We’re just slowly getting the pieces in
place.
What’s a good piece to do next? Lots of options, because now we’re to the point of implementing the handlers
for the various commands (besides the one for quitting, which we just did).
Personally, if we have things that display data and things that modify data, I prefer to do the ones that display
the data first. They’re less likely to mess things up (since we’re not modifying data), and if you can’t display
the data correctly, your odds of modifying it correctly are low, indeed.
So let’s hit up that “list” command that will show us lines from the file.
Problem-solving step: Devising a Plan
The list command takes a single argument representing the line number to start listing from. And then it
should list for 10 lines.
Since we have all the lines in a list already, this isn’t too entirely horrible. We just have to:
• Parse the starting position as entered by the user.
• If they entered a number less than 1, assume they meant 1.
• Start looping from that number, printing out 10 lines.
• If we hit the last line, stop printing.
Since we have all these different kinds of functionality, let’s put them in individual functions to keep the code
well organized.
We’ll make a handle_list() function that is called when the user asks to list the file. It’ll take a couple
of arguments: the arguments the user typed after a command, as well as the list of lines we’re going to
manipulate.
13.8. Chapter Project 151
if command[0] == 'q':
done = True
# List lines
elif command[0] == 'l':
handle_list(args, lines)
And let’s code up a stub6 of the function to handle it, just to see if it’s working:
def handle_list(args, lines):
print(f'Handle list: {args}, {lines}')
So you can see that the lines are all coming in right. But, more importantly, our argument is coming in right.
In the first call, it prints out as [] empty.
But on the second, we see ['99'] which is the number we told it to list.
We just have to extract that number somehow.
But before we do, we’d better test to see if the user entered an argument at all. It’s required for the list
command, after all.
Then we’ll use the start and end lines to print out everything in between.
def handle_list(args, lines):
if len(args) == 1:
# Compute start and end lines
6
A function stub is a callable function that takes all the same arguments and, if necessary, returns a sensible value. But it doesn’t, in
fact, do anything of use. It’s a good way to test that your overall call flow is working right. And it gives you a nice, easy TODO spot
to fill out.
152 Chapter 13. Reading Files
start = int(args[0])
end = start + 10 # print 10 lines
else:
print("usage: l line_num")
return
So clearly things have gone awry. There’s that huge error message that’s dominating the accident scene and
it’s hard to notice anything else other than the lines of the file being printed at the top.
At least nothing went wrong before that error, right?
…Or did it?
Notice anything weird about those first printed lines? Sure, the numbers start at 1, but the first line says This
is line 2! That sets off some alarm bells. (Especially since when you opened the file in your real editor,
you see the first line says This is line 1.)
Problem-solving step: Understanding the Problem.
We have two problems.
1. That list index out of range error
2. Our off-by-one error7 that’s causing our lines to be off by one.
Yes, off-by-one errors are famous enough to have their own Wikipedia page.
As the name suggests, our computations are one-off. But why? How?
This is a really common disagreement between humans and computers. We humans like to have our lists
start at index “1”, and computers like them to start at index “0”. It’s the age-old battle. Even the Romans
started with “I”, but that was mainly because Roman numerals didn’t have a character for zero until 725
AD—latecomers!
And in this case, we have a human entering numbers that start indexing at “1”. And we print them out for
the human to see starting with index “1”…
But in Python, the lines are in a list starting from index “0”!
7
https://en.wikipedia.org/wiki/Off-by-one_error
13.8. Chapter Project 153
• When we want to go from a human (1-based) index to a computer (0-based) index, we subtract one
from the number. 1 becomes 0.
• When we want to go from a computer (0-based) index to a human (1-based) index, we add one from
the number. 0 becomes 1.
Since we’re using start and end to index the list, I feel a bit better having them 0-based because the list is
0-based.
• When a user enters a 1-based number, convert it to 0-based as soon as you can for internal use in the
program.
• When you have to display a 1-based number, keep it 0-based for as long as you can, and only convert
it at the last second when you output it.
Keeping these conversions to a minimum and doing them only on input and output can save you a lot of
headaches.
What not to do: don’t just jump in and start adding +1s and -1s all over the place and hoping for the best.
That way lies madness, assuredly. Stop, understand, and plan. And then carry it out.
I’m going to write a little helper function here to convert from 0-based to 1-based since I think we’re going
to be using this all over the place. And it helps clarify the code a bit; instead of having a bunch of +1s and
-1s all over, you have a function name that has some meaning to future readers.
def one_to_zero(n):
"""Convert a number from a 1-based index to a 0-based index."""
return n - 1
And now we can make use of that. Let’s make start 0-based and try it out.
start = one_to_zero(int(args[0]))
Same pukey error, but let’s look at the lines before then. The good news is we’re getting all the lines printed.
The bad news is that the line number on the left is in computer 0-based land, and we need it in human 1-based
land. Let’s add another helper function:
154 Chapter 13. Reading Files
def zero_to_one(n):
"""Convert a number from a 0-based index to a 1-based index."""
return n + 1
Bam! That’s what we want. All lines printed with correct line numbers.
Now, what about that error? It’s telling us the list index is out of range, which isn’t too surprising since it’s
going off the end of the file.
Before our for-loop, let’s just add some code that makes sure the start and end are sane. (Remember
we’ve decided that they are 0-based indexes.)
# Make sure start isn't before the beginning of the list
if start < 0:
start = 0
And then you can run the for-loop after that with impunity!
$ python lineedit.py lines.txt
> l 1
1: This is line 1
2: This is line 2
3: This is line 3
4: This is line 4
5: This is line 5
> l 3
3: This is line 3
4: This is line 4
5: This is line 5
>
And now let’s write the delete handler. This is going to be similar to the line listing handler at first: we have
to get the line number the user entered, and convert it to 0-based.
And then make sure it’s in range.
And then delete that line with the pop() method.
def handle_delete(args, lines):
"""Delete a line in the file."""
if len(args) == 1:
# Get the line number to delete
line_num = one_to_zero(int(args[0]))
else:
print("usage: d line_num]")
return
Looks good!
What’s next easiest? Probably the “edit” functionality.
Problem-solving step: Understanding the Problem
When we edit a single line, we want to replace the element in the lines list completely with a new element
that we input from the keyboard.
The only line is thrown away.
For this, the user enters e for “edit”, followed by a line number.
Problem-solving step: Devising a Plan
Let’s do the same as with delete, except that instead of using pop() to remove a line, we’ll just use input()
to get another one and store it directly on the list.
Problem-solving step: Carrying Out the Plan
Firstly, let’s add that command handler to the main loop:
# Edit a line
elif command[0] == 'e':
handle_edit(args, lines)
Secondly, let’s implement the edit handler. Same code and rationale until the last line:
def handle_edit(args, lines):
"""Edit a line in the file."""
if len(args) == 1:
# Get the line number to edit
line_num = one_to_zero(int(args[0]))
else:
13.8. Chapter Project 157
print("usage: e line_num")
return
Notice how we just replace the named line in the list with whatever line is returned by input().
Let’s try it!
> l 1
1: This is line 1
2: This is line 2
3: This is line 3
4: This is line 4
5: This is line 5
> e 2
NEW LINE 2!
> l 1
1: This is line 1
2: NEW LINE 2!3: This is line 3
4: This is line 4
5: This is line 5
>
Wait a second! Lines 2 and 3 are all bunched up after I edited it! That can’t be right.
Problem-solving step: Understanding the Problem
This all ties back to the newlines we keep at the end of lines in the list.
Remember that we’re storing each line with the newline attached to the end.
But input() strips the newline off! Not what we were after.
Problem-solving step: Devising a Plan
So we have to add the newline to the end of the, er, new line that we just entered. We’ll just tack it on with
the + string concatenation operator.
Problem-solving step: Carrying Out the Plan
Modify the last line of the handle_edit() function to add the newline:
# Edit the line, adding a newline to the end (since input() strips
# it off).
lines[line_num] = input()# + '\n'
This kind of experimentation to see what works and what doesn’t is really common, and is a great way to
explore and learn how the system works.
13.8. Chapter Project 159
Ok, so we:
• Get the line number to append after. Remember that we want this to be zero-based, so we’ll subtract
1 from whatever they enter. If they enter “2”, that means that we want to insert after index 1.
• But since the insert() method inserts before a line, not after, we’d better add one to the line index
so that we append after that line.
• Then we loop until the user enters a period, inputting a line and inserting it into the list in the right
place.
Now, you might wonder why bother subtracting one just so we could add one right after?
And you’re right—it does nothing. We don’t have to do that.
But there’s an argument to be made that it’s clearer to a future reader of the code. We clearly subtract one to
get to a 0-based indexing method as soon as possible. And we add one to get insert() to insert after a line
instead of before it. Two different reasons to do the arithmetic clearly spelled out. If we were to just leave
them both off, that information wouldn’t be obvious to the next developer reading the code.
Problem-solving step: Carrying Out the Plan
First, let’s do our standard parsing of the command argument:
def handle_append(args, lines):
"""Append a line in the file."""
if len(args) == 1:
# Get the line number to append at. +1 because we want to line_num
# adding lines one _after_ the specified line.
line_num = one_to_zero(int(args[0]))
else:
print("usage: a line_num")
return
Then, continuing again, let’s put in our loop to read lines until the user enters a period, and insert them into
the correct location in the list.
done = False
# We're going to loop until the user enters a single `.` on a line
while not done:
All right, let’s test it. Let’s insert lines at the beginning:
> l 1
1: This is line 1
2: This is line 2
3: This is line 3
4: This is line 4
5: This is line 5
> a 0
NEW line 1
NEW line 2
.
> l 1
1: NEW line 1
2: NEW line 2
3: This is line 1
4: This is line 2
5: This is line 3
6: This is line 4
7: This is line 5
Works!
Let’s insert lines in the middle:
> a 4
NEW line in the middle
.
> l 1
1: NEW line 1
2: NEW line 2
3: This is line 1
4: This is line 2
5: NEW line in the middle
6: This is line 3
7: This is line 4
8: This is line 5
Works!
Let’s insert some lines at the end:
> a 8
NEW end line 1
NEW end line 2
.
> l 1
1: NEW line 1
2: NEW line 2
3: This is line 1
4: This is line 2
5: NEW line in the middle
6: This is line 3
7: This is line 4
13.8. Chapter Project 161
8: This is line 5
9: NEW end line 1
10: NEW end line 2
Now let’s write the handler for the command. This will check if the arg was specified and print an error if
not. And then write the file.
def handle_write(args, lines):
"""Handle the write command"""
if len(args) == 1:
filename = args[0]
162 Chapter 13. Reading Files
else:
print("usage: w filename")
return
write_file(lines, filename)
Lastly, we need to add a handler to the main command loop so that when we type “w”, it saves the file:
# Write (save) the file
elif command[0] == 'w':
handle_write(args, lines, filename)
Well, you can probably tell that’s not quite as easy as using VS Code (or any other editor, for that matter).
But, believe it or not, line editors were the way to enter programs for a long time.
Be thankful for standing on the shoulders of giants!
(Solution8 .)
13.9 Exercises
Remember: to get your value out of this book, you have to do these exercises. After 20 minutes of being
stuck on a problem, you’re allowed to look at the solution.
Use any knowledge you have to solve these, not only what you learned in this chapter.
8
https://beej.us/guide/bgpython/source/examples/lineedit.py
13.9. Exercises 163
Always use the four problem-solving steps to solve these problems: understand the problem, devise a plan,
carry it out, look back to see what you could have done better.
1. You’ve been misbehaving in class and the teacher sentences you to write 500 lines as punishment.
Shrewdly, you ask if you can type the lines, and the teacher agrees9 .
The program should accept command line arguments of the filename to output to and the number of
lines. All command line arguments after that are the punishment line itself that should be repeated that
many times in the output file.
For example:
python writelines.py outfile.txt 500 I will not talk in class.
(Solution10 .)
2. Write a program to read comma-separated values (CSV) files.
A CSV file has a bunch of values separated by commas, one record per row.
Here’s the file we want to read11 . Look through it and see how all the information for a particular
record is in each row:
Title,Release Year,Studio,Publisher
Minecraft,2011,Mojang,Microsoft Studios
M.U.L.E.,1983,Ozark Softscape,Activision
X-Men The Official Game,2006,Z-AXIS,Activision
Populous,1989,Bullfrog Productions,Electronic Arts
DOOM,1993,id Software,id Software
Lemmings,1991,DMA Design,Psygnosis
Your goal is to read the file and store each record in an object. (Make a class that defines the same
fields you have in the CSV file to instantiate the objects from.)
Then print out the data, like so:
M.U.L.E. 1983 Ozark Softscape Activision
Populous 1989 Bullfrog Productions Electronic Arts
Lemmings 1991 DMA Design Psygnosis
DOOM 1993 id Software id Software
X-Men The Official Game 2006 Z-AXIS Activision
Minecraft 2011 Mojang Microsoft Studios
The printout, above, is shown in sorted-by-year order. That’s a stretch goal if you want to take it on.
(Hint: check out the key keyword argument to the .sort() method. Also, the solution code talks
about it in detail.)
9
True story. This was back when I was in the 7th grade and computers were a bit of a novelty. We had a daisywheel printer at home
which was fairly indistinguishable from a typewriter. In real life, though, I didn’t write a program to generate the lines. I told dad about
the task, and he rolled his eyes and showed me how to copy and paste in WordStar.
10
https://beej.us/guide/bgpython/source/examples/ex_writelines.py
11
https://beej.us/guide/bgpython/source/examples/games.csv
164 Chapter 13. Reading Files
Also, incidentally, M.U.L.E.12 is one of the greatest games ever written. Despite it being over 25 years
old, PC World magazine rated it the 5th-greatest game of all time in 2009. If you haven’t played it,
grab an Atari 800 emulator, four gamepads, and four friends, and have some fun. (Or play solo against
the computer.)
Now, a quick word of warning: this exercise assumes you’re going to implement the logic
for parsing this file yourself. But in real life, in Python, you’d never do this. Python has
built-in functionality to parse CSV files, and it’s far more robust and correct than what we’re
doing here. We’re just reinventing the wheel in this case as a programming exercise.
Notice that the first line of the CSV file is a header. It describes what the columns are, but isn’t actual
data. You’ll have to skip this line when you’re reading the file. (Hint: call the next() function on the
file iterator returned by open() to get the next line one time at the beginning.)
(Solution13 .)
3. Modify your multiplication table generator from the chapter on strings to save the table to disk instead
of printing it to the screen.
The program should accept both the dimension of the table and the output filename on the command
line, e.g.:
python multtablefile.py 12 table12x12.txt
(Solution14 .)
4. Write a program to count the number of words in a file specified on the command line. The number of
words should be printed out.
This is a simplified clone of the Unix wc (word count) command.
For this one, we’ll define a word as something separated by whitespace. (Hint: the .split() string
method15 .)
Example (wargames.txt is in the examples directory16 ):
$ python ex_wc.py wargames.txt
38
(Solution17 .)
5. Write a program that sorts lines of a file in alphabetical order and prints the result on the screen. Note
that you don’t need to alphabetize every word in each line—just treat the line as one big word to be
alphabetized.
This is a simplified version of the Unix sort command.
Example run:
$ python ex_sort.py rocks.txt
amphibolite
andesite
argillite
basalt
breccia
chalk
12
https://en.wikipedia.org/wiki/M.U.L.E.
13
https://beej.us/guide/bgpython/source/examples/ex_simplecsv.py
14
https://beej.us/guide/bgpython/source/examples/ex_multtablefile.py
15
https://docs.python.org/3/library/stdtypes.html#str.split
16
https://beej.us/guide/bgpython/source/examples/wargames.txt
17
https://beej.us/guide/bgpython/source/examples/ex_wc.py
13.10. Summary 165
chert
claystone
(And so on. The rocks.txt file has more lines in it than I’ve shown here.)
(Solution18 .)
13.10 Summary
What a chapter! That was like a 50% project, eh?
But look at what we learned!
We covered how to open and read and write text files. We talked about how to read and write a line at a time,
as well.
And we wrote a simple line-based text editor! Most developers go their entire careers without doing that.
More Python goodness in the next chapter—see you there!
18
https://beej.us/guide/bgpython/source/examples/ex_sort.py
166 Chapter 13. Reading Files
Chapter 14
Exceptions
14.1 Objective
• Learn about different ways of handling errors
• Understand what exceptions are
• Write programs that catch exceptions
• Throw your own exceptions
14.2 Project
Write a program called head.py that returns the first few lines of a file. For example, if the user enters:
python head.py filename.txt 12
167
168 Chapter 14. Exceptions
There’s one way we’ve been using so far: the return value from a function. If it’s a particular sentinel value
that we’re on the lookout for, we can use that to determine success or failure.
For example, let’s check out the .find() method on strings. This returns the index in a string that a given
substring can be found. For example:
s = 'Bears, beets, Battlestar Galactica'
x = s.find('beets')
The return value there of 7 is “good” data. We asked for a thing and we got it. But what if something goes
wrong?
s = 'Bears, beets, Battlestar Galactica'
x = s.find('Dwight')
-1 here is the sentinel value we’re looking for to tell us if there’s an error.
We can make decisions on it. This is what I’d call “classic” error handling. This is the way people used to
handle errors when Stonehenge was built. And, like Stonehenge, this method of handling errors is still in use
to this day. If it ain’t broke, don’t fix it.
x = s.find(substring)
if x == -1:
print(f"Couldn't find {substring}")
else:
print(f"Found {substring} at index {x}")
We’ve already seen some of these. For example, if we run code that does this:
int("Hello!") # Convert Hello! to integer
Python’s going to be upset. "Hello!" isn’t a number it’s ever heard of. And when we run it, we get this
message, and the program exits:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'Hello!'
14.6. Catching Exceptions 169
That’s an exception in action. We tried some code, and it raised an exception to tell us that what we were
doing just wasn’t going to work.
Exceptions are raised (also sometimes said to be thrown) for all kinds of things in Python.
Try to open a nonexistent file for reading:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: 'keyser_soze.txt'
Those first condensed words on the last line of the exception you see there? That’s the name of the exception
that occurred.
ValueError
FileNotFoundError
ZeroDivisionError
So, like using return values to indicate errors, exceptions also indicate that an error occurred.
Now—how do we detect that and do something with it?
6 print(x * 1000)
7
8 except ValueError:
9 print(f'error converting "{x}" to integer')
What’s happening there? Look at the big blocks first. We have a try block and an except block.
Think of the try block as the code you want to execute in your shiny dreamworld where your user enters
correct information every time.
Like the user enters 3490, and it converts to integer just fine, and then you print out 3490000.
Perfect.
But what if the user enters beans instead of a number? int() is going to freak out and raise a ValueEx-
ception, just like we saw earlier.
Here’s the magic. If that happens, execution of the try block will stop immediately, and Python will transfer
control to the matching except block, if it exists.
170 Chapter 14. Exceptions
x = int(x)
y = int(y)
Check it out! I entered a single number, and it raised ValueError exception (with a message saying there
weren’t enough values).
Let’s try too many values:
1
https://docs.python.org/3/library/exceptions.html
14.7. Catching Multiple Exceptions 171
ValueError again! That means we can do something like this to catch it:
try:
x, y = input('Enter two numbers separated by a space: ').split()
x = int(x)
y = int(y)
except ValueError:
print("That's not two numbers separated by a space!")
Whee!
What’s the next place we can mess things up?
Well, we’re converting to int()… what does that function do if we pass in something awful, like the word
manfrengensenton?
Hey, it’s ValueError again! Conveniently, we’re already catching that with an appropriate error message.
Totally handled.
That takes care of three of the four cases I saw where we could get exceptions. What’s the fourth?
Mathematics hat on. Do you see it?
That’s right, we’re dividing there… and you can’t divide by zero. What happens when we do?
We already saw, above, that we get a ZeroDivisionError. So let’s add that to the end of our code:
try:
x, y = input('Enter two numbers separated by a space: ').split()
x = int(x)
y = int(y)
except ValueError:
print("That's not two numbers separated by a space!")
172 Chapter 14. Exceptions
except ZeroDivisionError:
print("Can't divide by zero!")
So as you can see, you can handle as many different types of exceptions as you want in their own except
clauses after the try.
This isn’t as frequently used, since often you want to take a different course of action for different exceptions.
results in:
division by zero
ZeroDivisionError('division by zero')
That could be useful for getting more detailed information. In our example in the previous chapter, we catch
ValueError, but we saw three different circumstances that could lead to it. We could use this technique to
give the user more detailed information about the nature of the exception, should we choose.
All exceptions have an attribute called args that is a list of the arguments that are passed to the exception
when it was created. The first of these is often a human-readable error message.
For instance, this code:
try:
1 / 0
except Exception as e:
print(e.args[0])
Furthermore, any exception that is based on IOError includes the string attribute strerror that contains a
human-readable error message corresponding to the error. You can find the list of exceptions that are derived
from IOError in the exceptions documentation2 .
2
https://docs.python.org/3/library/exceptions.html
14.10. Catching All Exceptions 173
try:
x, y = input('Enter two numbers separated by a space: ').split()
x = int(x)
y = int(y)
except:
print(sys.exc_info())
This isn’t as common, to catch and examine exceptions in this way. But it is another tool in your toolbox.
Be careful with catch-alls. They might hide exceptions that you weren’t expecting and should have let
through. They’re rare in practice.
try:
print(1/1)
except ZeroDivisionError:
print("Divide by zero!")
finally:
print("All done!")
If we modified that first line to print(1/0), we’d get a divide by zero exception and the output would be:
Divide by zero!
All done!
It’s entirely possible that you won’t ever see this, but I wanted to quickly touch on it here. Just glance at it:
try:
print("This is what we're trying to do")
print("and where exceptions might occur.")
except:
print("Caught an exception!")
else:
print("This only runs if there was no exception.")
finally:
print("This runs no matter what.")
Using else can give you more control over the flow of your program when exceptions occur.
We’ve already hinted at this, above, where we talk about getting more information from exceptions.
We found there is a class named ZeroDivisionError and another named ValueError. Indeed, we can just
make new ones of these if we want:
e = ValueError() # Construct a new ValueError object
There’s a whole list of exceptions that are ready to use4 , but if none of those seem to fit, you can just make
a new Exception with some information passed to it:
e = Exception('Something went horribly awry')
Lastly, you can make your own new exception classes if you’d like. You don’t have to—you can use Ex-
ception or any of the other preexisting ones.
But if you do make your own, the only catch is that these must inherit from the Exception base class.
Whooooaa, there, Beej. What are you even talking about?
Okay, you got me. I stepped into some Object-Oriented Programming terminology, there. Now, we’ll talk
about what that all means in a later chapter, but for now, take my word that you need to declare your new
exception, you have to use similar syntax to this:
class MyAwesomeException(Exception): # <-- Note "(Exception)"
pass
This is telling Python, “I’m making a new class called MyAwesomeException, but, here’s the thing, MyAwe-
someException is an Exception.”
# The following line makes sure the constructor for the underlying
# Exception object gets called with the arguments specified:
Because MyAwesomeException is an Exception, suddenly we have two constructors: one for Exception
and one for MyAwesomeException.
The one in MyAwesomeException overrides the one in Exception. In order to make sure both are called,
we add that super() line in there.
Don’t worry about the details of how it works for now. We’ll cover that in detail in another chapter.
One final note: if you ever catch Exception: in your code, make sure that catch is after all the more spe-
cific exceptions, like ValueException. Python will use the first one it finds that matches, and Exception
matches most everything.
But in the meantime, we can construct exceptions. But so what? What can we do with them?
Let’s say you’ve written some code and you want to use exceptions to notify the caller when some error
condition has occurred.
The process is going to be:
1. Construct a new exception of some kind.
2. Raise it with the raise statement.
Often these happen in the same line.
As an example, let’s write a function that reads a number between 0 and 9 from the keyboard. If the number
read is out of range, let’s raise a new ValueError with the message "out of range" as the argument.
def getnum():
n = input("Enter a number 0-9: ")
if n < 0 or n > 9:
# If out of range, raise a ValueError:
raise ValueError("out of range")
return n
And then add some code to call it and catch any exceptions:
try:
n = getnum()
print(f'{n} * 15 == {n * 15}')
except ValueError as v:
print(f'Exception: {v}')
What if we enter the letter a? That’ll bomb out on the call to int()… but it’ll do it with a ValueError, like
we saw earlier in the chapter.
And, hey! Coincidentally, we’re already catching ValueError in our code, above.
Let’s try it:
Enter a number 0-9: a
Exception: invalid literal for int() with base 10: 'a'
Caught it! Note that the error message is different than the “out of range” exception, so we can differentiate.
So, hey! We now know how to:
• Catch exceptions
• Create new exceptions
• Raise exceptions
That’s not bad so far!
14.15. Re-raising Exceptions 177
except ValueError:
print("Hey, I saw an exception!")
print("But I'll let someone else handle it.")
try:
x = makeint("beej")
except ValueError as v:
print(f'Exception: {v}')
This outputs:
Hey, I saw an exception!
But I'll let someone else handle it.
Exception: invalid literal for int() with base 10: 'beej'
That last one, about what happens when you enter a number larger than the number of lines in the file, is a
great question. The spec doesn’t say. So we should ask the creator of the spec for clarification.
“Hey, Beej! The spec doesn’t say what to do if the number of lines specified is greater than the number of
lines in the file. What do we do in that case?”
Let’s do this: we’ll stop outputting lines at either the number of lines the user specifies or the end of the file,
whichever comes first. No message to the user is required in either case.
Ok, let’s plan!
Problem-solving step: Devising a Plan
Looking at the spec, the program can be broken down into a number of parts.
• Read user input
• Open the file for reading
• Read the number of lines up to what the user-specified (or EOF)
For each of those parts, we’ll have to do input validation and tell the user if anything went wrong.
Problem-solving step: Carrying Out the Plan
Some of this stuff we’ve seen before, so we’ll skim over it a bit.
First, let’s get the user input from the command line, check that the right number of arguments was passed,
and check the input to make sure it’s sensible.
1 import sys
2
3 if len(sys.argv) != 3:
4 print("usage: head.py filename count")
5 sys.exit(1)
6
7 filename = sys.argv[1]
8 total_count = int(sys.argv[2])
9
10 if total_count < 1:
11 print("head.py: count must be a positive integer")
12 sys.exit(2)
That’s partway there, but we’re missing an error case. Do you see it?
What if the user enters “bananas” for the count? If we try to run it to see what happens, sure enough, we get
an exception.
Traceback (most recent call last):
File "foo.py", line 8, in <module>
total_count = int(sys.argv[2])
ValueError: invalid literal for int() with base 10: 'bananas'
It’s the ValueError exception that we’ve seen before. Let’s modify our code to catch that exception and
handle it.
1 import sys
2
3 if len(sys.argv) != 3:
4 print("usage: head.py filename count")
5 sys.exit(1)
6
7 filename = sys.argv[1]
14.16. Project Implementation 179
9 try:
10 total_count = int(sys.argv[2])
11 except ValueError:
12 print("head.py: count must be a positive integer")
13 sys.exit(2)
14
15 if total_count < 1:
16 print("head.py: count must be a positive integer")
17 sys.exit(2)
12 if total_count < 1:
13 raise ValueError()
14
15 except ValueError:
16 print("head.py: count must be a positive integer")
17 sys.exit(2)
Check that out. If int() raises the exception, we catch it. And if we raise the exception ourselves, we also
catch it. Plus all the logic for testing the input value for correctness is all in the same try block, nicely.
OK! We have the code getting the correct input. Let’s go on to the next step and print lines from the file.
We can start by simplifying the problem to just print all the lines and not worrying about the count for now.
Let’s take our code from before for printing out a file:
19 with open(filename) as f:
20 for line in f:
21 print(line, end="")
If we run the program, passing in an existing file, we see all the lines of that file printed out.
But what if we pass in the name of a non-existent file?
Let’s try it!
$ python head.py nosuchfile.txt 5
Traceback (most recent call last):
File "foo.py", line 19, in <module>
with open(filename) as f:
FileNotFoundError: [Errno 2] No such file or directory: 'nosuchfile.txt'
$ python head.py / 5
Traceback (most recent call last):
File "foo.py", line 19, in <module>
with open(filename) as f:
IsADirectoryError: [Errno 21] Is a directory: '/'
An IsADirectoryError exception!
Let’s try it on a file we don’t have permission to read:
$ python head.py noperm.txt 5
Traceback (most recent call last):
File "foo.py", line 19, in <module>
with open(filename) as f:
PermissionError: [Errno 13] Permission denied: 'noperm.txt'
24 except IOError as e:
25 print(f'head.py: {filename} {e.strerror}')
And when we run it, we get some nice error message for whatever error case we get:
$ python head.py noperm.txt 5
head.py: noperm.txt Permission denied
$ python head.py / 5
head.py: / Is a directory
5
https://docs.python.org/3/library/exceptions.html
14.17. Exercises 181
Pretty neat!
What’s left? Oh yeah—we have to actually implement the functionality to only show the first however-many
lines of the file.
There are a couple of approaches to this.
One, we could use a while loop and test for the end of the file or reaching the required count, whichever
comes first.
That would be fine. But a more straightforward option might be to just jump out of the loop when the counter
gets high enough. The break statement can be used to bail out of a loop partway through.
19 line_count = 0 # Number of lines we've read so far
20
21 try:
22 with open(filename) as f:
23 for line in f:
24
25 line_count += 1
26
30 print(line, end="")
31
32 except IOError as e:
33 print(f'head.py: {filename} {e.strerror}')
As you see, we’re keeping track of the number of lines read so far, and if that exceeds our magic target
number, we just break straight out of the loop and we’re done.
And it works!
$ python head.py rocks.txt 3
marble
coal
granite
Super-robust against bad input and errors. This is what we call defensive coding, when you prepare for the
worst and handle those cases without crashing. It’s a good strategy because not only does it make your
program more capable of handling errors, but it also makes you stop and consider what the errors are that
might occur in the first place. And, as we’ve said, hours of debugging can save you minutes of planning.
(Solution6 .)
14.17 Exercises
1. When we run this code, it prints out “Exception” instead of “Division by Zero”. Why? What can we
do, without deleting any code, to get it to print “Division by zero”?
try:
x = 3490 / 0
6
https://beej.us/guide/bgpython/source/examples/head.py
182 Chapter 14. Exceptions
except Exception:
print("Exception")
except ZeroDivisionError:
print("Division by Zero")
(Solution7 .)
2. Write a function that takes a list of numbers, and two integers as index values. The function should
return the sum of the two numbers in the list at the two given indexes.
Catch the specific exception that is raised if the list indexes are out of range. Print an appropriate error.
Hint: to see which exception is raised if the list indexes are out of range, run the code without a try-
except block and see what it prints when it bombs. Then add a try-except for that exception.
(Solution8 .)
3. Write a function that accepts a list of three numbers and returns the sum. If the list does not contain
three numbers, raise a InvalidListSize exception. (Note that this exception doesn’t exist—you’ll
have to write it.)
Also write an exception handler that catches the exception if it is thrown.
(Solution9 .)
14.18 Summary
A new big concept in this chapter with exceptions. It’s a technique we haven’t used it before to catch errors,
but is a powerful one to add to your skillset.
We compared and contrasted error handling via return values with error handling with exceptions, writing
programs that could catch exceptions and handle them, and also wrote programs that generate our own, new
exceptions.
Additionally, we learned how flow control works around exception handling, with the else and finally
clauses.
Any time you learn a new basic way of doing something, it’s difficult to wrap your head around at first. But
enough practice with it, and I guarantee after a while it will become second nature.
7
https://beej.us/guide/bgpython/source/examples/ex_catchorder.py
8
https://beej.us/guide/bgpython/source/examples/ex_listadd2.py
9
https://beej.us/guide/bgpython/source/examples/ex_listadd.py
Chapter 15
15.1 Arithmetic
Addition, subtraction, multiplication, and—don’t fall asleep on me already—division. The Big Four of grade
school math.
And, as a quick terminology summary:
• The addition of two numbers produces a sum.
• The subtraction of two numbers produces a difference.
• The multiplication of two numbers produces a product.
• The division of two numbers produces a quotient (and potentially a remainder.)
I’m not going to talk about what the operators do, and will presume you remember that from however-many
years ago.
But I do want to talk about which comes first, because you might have forgotten.
What do I mean by that?
Take this expression: 1 + 2 × 3
If we first add 1 + 2, we get 3. And then we multiply it by 3 and we get the answer of 9.
But wait! If we first multiply 2 × 3 and get 6, then we add 1 we get an answer of 7!
So… which is it? 9 or 7?
183
184 Chapter 15. Appendix A: Basic Math for Programmers
What we need to know is the order of operations or the precedence of one operator over another. Which
happens first, + or ×?
For arithmetic, here is the rule: do all the multiplications and divisions before any additions and subtractions.
So: 1 + 2 × 3 = 7
We’ll revisit order of operations as we continue on.
In Python, we can just use the operators +, -, *, and / for addition, subtraction, multiplication, and division,
respectively.
What we’ve done there is a floating point division. That is, it produces a floating point result with a decimal
point. Indeed, even this does the same:
print(2 / 1) # 2.0
When you want an integer quotient, this is the fast way to do it.
Mod has the neat property of rolling numbers over “odometer style” because the result of the mod can never
be larger than the divisor.
As an example:
for i in range(10):
print(i % 4) # i modulo 4
outputs:
0
1
2
3
0
1
2
3
0
1
In terms of precedence, you can think of absolute value kind of like parentheses. Do the stuff inside the
absolute value first, then take the absolute value of the result.
78 = 7 × 7 × 7 × 7 × 7 × 7 × 7 × 7
We have all kinds of fun facts! Ready?
In the example 78 , the number 7 is what we call the base and the number 8 we call the exponent.
If you want to write that code in Python, you’d write:
print(7**8) # prints 5764801
Lastly, precedence. We know that multiplication and division happen before addition and subtraction, but
what about exponentiation?
Here’s the rule: exponentiation happens before arithmetic.
With 2 + 34 , we first compute 34 = 81, then add 2 for a total of 83.
15.7 Parentheses
So what if you want to change the order of operations, like some kind of mathematical rebel?
What if you have 1 + 23 and you want 1 + 2 to happen before the exponentiation?
You can use parentheses to indicate that operation should occur first, like this:
(1 + 2)3
With that, we first compute 1 + 2 = 3, and then we raise that to the 3rd power for a result of 27.
You could also do this:
188 Chapter 15. Appendix A: Basic Math for Programmers
2(3+4)
Remember: parentheses first! 3+4 = 7, so we want to compute 27 which is 128. (Good software developers
have all the powers of 2 memorized up through 216 . Well, crazy ones do, anyway.)
Python uses parentheses, as well. The above expression could be written in Python like this:
2**(3+4)
Easy peasy.
One final note: if an exponent is a long expression without parentheses, it turns out the parentheses are
implied. The following equation is true:
2(3+4) = 23+4
You might think, “We’re doomed,” but not mathematicians! They simply defined:
√
−1 = 𝑖
and invented an entire mathematical system around what they called imaginary numbers3 that actually have
some amazing applications. But that’s something you can look up on your own.
In addition to square roots, there are also cube roots. This is asking, “What number cubed results in this
number?” This is indicated by a small 3 above the root symbol:
√
3
27 = 𝑥
which is the inverse of the equation:
𝑥3 = 27
Can you figure out what 𝑥 is?
What about how to do this in Python?
For square roots, the preferred way is to use the sqrt() function in the math module that you import:
import math
What about cube roots? Well, for that, we’re going to jump back to exponents and learn a little secret. You
can raise numbers to fractional powers. Now, there are a lot of rules around this, but the short of it is that
these equations are true:
√ 1 √ 1 √ 1
𝑥 = 𝑥2 3
𝑥 = 𝑥3 4
𝑥 = 𝑥4
1
and so on. Raising a number to the 3 power is the same as taking the cube root!
Like if we wanted to compute the cube root of 4913, we could compute:
√
3 1
4913 = 4913 3 = 17
And you can do that in Python with the regular exponent operator **:
print(4913**(1/3)) # prints 16.999999999999996
15.9 Factorial
Factorial is a fun function.
Basically if I ask for something like “5 factorial”, that means that I want to know the answer when we multiply
5 by all integers less than 5, down to 1.
So I’d want to know:
5×4×3×2×1
the answer to which is 120.
3
https://en.wikipedia.org/wiki/Imaginary_number
190 Chapter 15. Appendix A: Basic Math for Programmers
But writing “factorial” is kind of clunky, so we have special notation for it: the exclamation point: !. “5
factorial” is written 5!.
Another example:
6! = 6 × 5 × 4 × 3 × 2 × 1 = 720
As you can imagine, factorial grows very quickly.
40! = 815915283247897734345611269596115894272000000000
In Python, you can compute factorial by using the factorial() function in the math module.
import math
Factorial, being multiplication in disguise, has the same precedence as multiplication. You do it before
addition and subtraction.
7.2e-19 means:
7.2 × 10−19
1
And 10−19 is a very small number (very close to 0—remember that 10−19 = 1019 ), so multiplying 7.2 by
it results in a very small number as well.
Remember this:
• If you see e-something at the end, it’s a very small number (close to 0).
• If you see e+something at the end, it’s a very large number (far from 0).
15.11 Logarithms
Hang on, because things are about to get weird.
Well, not too weird, but pretty weird.
Logarithms, or logs for short, are kind of the opposite of exponents.
But not opposite in the same way square roots are opposite.
That’s convenient, right?
15.11. Logarithms 191
With logs, we say “log base x of y”. For example, “log base 2 of 32”, which is written like this:
log2 32
𝑥 = log2 32
2𝑥 = 32
So what’s the answer? Some trial and error might lead you to realize that 25 = 32, so therefore:
𝑥 = log2 32 = 5
There are three common bases that you see for logs, though the base can be any number: 2, 10, and 𝑒.
Base 2 is super common in programming. In fact, it’s so common that if you ever see someone write log 𝑛
in a computing context, you should assume they mean log2 𝑛.
𝑒 is the base of the natural logarithm4 , common in math, and uncommon in computing.
So what are they good for?
A common place you see logarithms in programming is when using Big-O notation to indicate computational
complexity5 .
To compute the log of a number in Python, use the log() function in the math module. You specify the base
as the second argument. (If unspecified, it computes the natural log, base 𝑒.)
The big thing to remember there is that as a number gets large, the log of the number remains small. Here
are some examples:
x log2 x
1 0.0000
10 3.3219
100 6.6439
1000 9.9658
10000 13.2877
100000 16.6096
1000000 19.9316
10000000 23.2535
4
https://en.wikipedia.org/wiki/Natural_logarithm
5
https://en.wikipedia.org/wiki/Big_O_notation
192 Chapter 15. Appendix A: Basic Math for Programmers
15.12 Rounding
When we round a number, we are looking for the nearest number of a certain number of decimal places,
dropping all the decimal places smaller than that.
By default, we mean zero decimal places, i.e. we want to round to the nearest whole number.
So if I said, “Round 2.3 to the nearest whole number,” you’d answer “2”, because that’s the closest whole
number to 2.3.
And if I said, “Round 2.8 to the nearest whole number,” you’d answer “3”, because that’s the closest whole
number to 2.8.
When we round to a higher number, we call that rounding up.
The other direction is rounding down.
But what if I said “Round 2.5 to the nearest whole number?” It’s perfectly between 2 and 3! In those cases,
conventionally, lots of us learned to round up. So the answer would be 3.
We can also force a number to round a certain direction by declaring which way to round.
“Divide x by 3, then round up.”
In Python, we have a few options for rounding.
We can use the built-in round() function.
But it behaves a little bit differently than we might be used to. Notably, numbers like 1.5, 2.5, and 3.5 that
are equally close to two whole numbers always round to the nearest even number. This is commonly known
as round half to even or banker’s rounding.
round(2.8) # 3
round(-2.2) # -2
round(-2.8) # -3
round(1.5) # 2
round(2.5) # 2
round(3.5) # 4
You can also tell round() how many decimal places you want to round to:
round(3.1415926, 4) # 3.1416
Note that Python has additional weird rounding behavior6 due to the limited precision of floating point num-
bers.
For always rounding up or down, use the functions ceil() and floor() from the math module.
ceil() returns the next integer greater than this number, and floor() returns the previous integer smaller
than this number.
This makes perfect sense for positive numbers:
import math
math.ceil(2.1) # 3
math.ceil(2.9) # 3
math.ceil(3.0) # 3
math.ceil(3.1) # 4
math.floor(2.1) # 2
6
https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
15.12. Rounding 193
math.floor(2.9) # 2
math.floor(3.0) # 3
math.floor(3.1) # 3
But with negative numbers, it behaves differently than you might expect:
import math
round(2.3) # 2
math.floor(2.3) # 2
round(-2.3) # -2
math.floor(-2.3) # -3 (!!)
round(2.8) # 3
math.ceil(2.8) # 3
round(-2.8) # -3
math.ceil(-2.8) # -2 (!!)
While round() heads to the nearest integer, floor() goes to the next smallest integer. With negative num-
bers, that’s the next one farther away from zero. And the reverse is true for ceil().
If you want a round up function that works on positive and negative numbers, you could write a helper
function like this:
import math
def round_up(x):
return math.ceil(x) if x >=0 else math.floor(x)
round_up(2.0) # 2
round_up(2.1) # 3
round_up(2.8) # 3
round_up(-2.0) # -2
round_up(-2.1) # -3
round_up(-2.8) # -3
If you want to always round up to the next whole number instead of doing banker’s rounding, you can use
this trick: add 0.5 to the number and then convert it to an int(). (If the number is negative, subtract 0.5
from it.)
int(0.5 + 0.5) # 1
int(1.5 + 0.5) # 2
int(2.5 + 0.5) # 3
int(2.8 + 0.5) # 3
int(2.2 + 0.5) # 2
int(-2.2 - 0.5) # -2
int(-2.8 - 0.5) # -3
194 Chapter 15. Appendix A: Basic Math for Programmers
What happened after you hit RETURN was that Python Evaluated your expression, and the Printed the result.
And then it printed another >>> prompt, because it’s doing this in a Loop! The REPL!
This is a method you could use to quickly test out Python commands to see how they work. It’s not commonly
used for development, but it is there if you find it convenient.
Any time you see the >>> prompt, it’s waiting for another Python command.
>>> a = 20
>>> b = 30
>>> c = a + b
>>> print(c)
50
195
196 Chapter 16. Appendix B: The REPL
Wait! What’s that ... prompt? That means Python is waiting for more. The fact that the previous line ended
in a : indicates that a block is to follow it. So Python is waiting for a properly-indented block. (Hit RETURN
on a blank line to exit the block. If you get stuck in ... mode, just keep hitting RETURN until you get back
out to the >>> prompt.)
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
16.2 Calculator
You can use the Python REPL as a quick and dirty calculator.
>>> 50 + 20 * 10
250
>>> import math
>>> math.factorial(10)
3628800
>>> math.sqrt(2)
1.4142135623730951
class str(object)
| str(object='') -> str
| str(bytes_or_buffer[, encoding[, errors]]) -> str
|
| Create a new string object from the given object. If encoding or
| errors is specified, then the object must expose a data buffer
| that will be decoded using the given encoding and error handler.
Et cetera. The : prompt at the bottom is the prompt for the pager. You can hit RETURN to go to the next line,
or SPACE to go to the next page. Up and Down arrow keys also work. Type q to get out of the pager and
return to normal.
The first thing you might notice are a bunch of functions that have double underscores around them, like this:
| __add__(self, value, /)
| Return self+value.
16.4. dir()—Quick and Dirty Help 197
Those double underscores, AKA dunderscores or just dunders, indicate that this is an internal or special
functions. As a beginner, you can ignore them. As you get more advanced, you might want to see how to
make use of them.
So hit SPACE a bunch of times until you’re past the dunders. After that, you start getting to the documentation
for the more common functions, like this one:
| count(...)
| S.count(sub[, start[, end]]) -> int
|
| Return the number of non-overlapping occurrences of substring sub in
| string S[start:end]. Optional arguments start and end are
| interpreted as in slice notation.
We see there’s a description there of what the method does, as well as an important description of what each
parameter to the function means. But let’s look at this line in particular:
| S.count(sub[, start[, end]]) -> int
That’s not Python. It’s documentation, and it has its own way of being read.
Generally:
The S. refers to this string that we’re operating on. For example, if I say:
"fee fie foe foo".count("fo")
Not as good as help(), but it might get you the quick answer if you’re like, “What do I call to get the values
out of a dictionary, again? Oh, that’s right. values()! Eureka!”
One place this is also useful is if you’re using a poorly-documented piece of software1 . Asking for dir()
on an object can give you insight on how to use it.
Though if you do this, beware that programmers change their undocumented interfaces all the time without
telling people. There’s a school of thought that says if something’s undocumented, you shouldn’t use it at
all, lest it be silently changed or dropped some day in the distant future. And that school has a point.
1
Which should serve as a not-so-gentle reminder that you should document your code.
2
https://en.wikipedia.org/wiki/End-of-file
Chapter 17
In this book, we’ve talked about how Python variables work, but let’s dig into it a little more here.
When we have data of some kind, like a number or a list or a string, that’s stored in memory. And we can
assign it to a variable name so that we can have a way to refer to it.
That variable name is a reference to the data.
So if everything’s a reference, that must mean that if we do this, there’s only one string, right? Just two
names for the same string?
s = "Beej!"
t = s
Yes! That’s exactly what that means. s and t both refer to the same string in memory. That means if you
changed the string, both s and t would show the changes because they’re both two names for the same string.
But you can’t change the string! It’s immutable!
It’s the same with numbers:
x = -3490
y = x
Both x and y refer to the same number in memory. If you changed the number, both x and y would show the
change.
But you can’t change the number! It’s immutable!
Let’s try a list:
c = [1, 2, 3]
d = c
Just like with strings and numbers, both variables c and d refer to the same thing in memory. But the difference
is that the list is mutable! We can change it, and we’d see the change in c and d.
c = [1, 2, 3]
d = c
c[1] = 99
print(d[1]) # 99
Of course, you can reassign a variable to point at anything else at any time.
199
200 Chapter 17. Appendix C: Assignment Behavior
6 a = [1, 2, 3]
7 foo(a)
The exact number doesn’t matter (it will vary), but what matters is that they’re identical. Both s and t refer
to the entity in memory at that location, namely the string "Beej!".
You could compare those numbers to determine if both variables point to the same thing:
>>> id(s) == id(t)
True
Note that it’s typically only want you assign from one variable to another that they refer to the same thing.
If you assign to them independently, they typically won’t:
>>> s = "Beej!"
>>> t = "Beej!"
>>> s is t
False
In the above example, there are two strings in memory with value "Beej!".
17.3. Python Compiler Optimizations 201
I recognize that I said “typically” a bunch up there, and that should rightfully raise a bunch of “Beej is
hand-waving” red flags.
The actual details get a bit more gritty, but if you want to stop with what we’ve said up there, you’re good.
“No, keep going down the rabbit hole!”
Okay then!
and run it from the command line, you’d think you’d get False, just like in the REPL. Wrong!
$ python test.py
True
What gives? Why is it False in the first case and True in the second? Well, in the latter case, the Python
interpreter is getting a little clever. Before it even runs your code, it analyzes it. It sees that you have two
"Beej!" strings in there, so it just makes them the same one to save you memory. Since strings are immutable,
you can’t tell the difference.
17.4 Internment
In that same example, above:
>>> s = "Beej!"
>>> t = "Beej!"
>>> s is t
False
True?? What’s up with that? Why does Alice get special treatment?
Or look at this:
>>> x = 257
>>> y = 257
>>> x is y
False
which is fine—but then check this out, with 256 instead of 257:
202 Chapter 17. Appendix C: Assignment Behavior
>>> x = 256
>>> y = 256
>>> x is y
True
True, again?
We’re getting into a deep language feature of Python called internment. Basically Python makes sure to only
have one copy in memory of certain, specific values of data.
For these values, all variables will refer to the same item in memory.
They are:
• Integers between -5 and 256 inclusive.
• Strings that contain only letters, numbers, or underscores.
• The None object.
This is why "Beej!" isn’t interned (because it contains punctuation), and why "Alice" is interned.
You can intern your own strings with sys.intern() for dictionary lookup optimization, but that’s something
99.99999% of the Python programming populace will never bother doing.
Chapter 18
203
204 Chapter 18. Appendix D: Number Bases
0, 1… oh no! We’re out of digits. We have to add another place. Except this time, in binary, it’s not the 10s
place… it’s the 2s place.
0, 1, 10, 11… out of digits again! We have to add another place. This time it’s the 4s place:
0, 1, 10, 11, 100.
Let’s look at that last number. In binary, that’s saying we have 1 4, 0 2s, and 0 1s. For a total value of 4. For
5, we just have to put a 1 in the 1s place: 101.
In fact, we can take any number and digest it that way. Take the human number 7. That’s made up of one 4,
one 2, and one 1. 4 + 2 + 1 = 7. So in binary, we need a 1 in the 4s place, the 2s place, and the 1s place.
Which looks like this in binary: 111.
We’ve written the number 7 in two “languages”. In human language, it looks like 7. In computer language
it looks like 111.
But, and this is important: human 7 and computer 111 are the same number. They’re just written in a different
language, of sorts.
With the decimal number 1234, we have 1 in the thousands place, 2 in the hundreds, 3 in the tens place, and
4 in the ones place. But the place values 1000, 100, 10, and 1 are all powers of 10:
100 = 1 101 = 10 102 = 100 103 = 1000
And it’s the same in binary. If we have the binary number 1011, that’s 1 in the 8s place, 0 in the 4s place, 1
in the 2s place, and 1 in the 1s place.
20 = 1 21 = 2 22 = 4 23 = 8
So the base is also tied into the value that any place in a number represents. Which makes sense, since we
have to add a new place when we run out of digits, and if we have digits 0-9, that next place must represent
the number of 10s, because that’s what comes after 9.
19.1 Objectives
• Install a real IDE
• Learn about the command line.
• Get your terminal/shell up and running, and explain how it’s used
207
208 Chapter 19. Accelerating Beyond IDLE
The shell is a program that takes your typed-in commands and executes them, and tells you if something
went wrong.
The most popular shell program on Unix-likes and Macs is the Bourne Again Shell (Bash)1 , although the Z-
shell (Zsh) is growing in popularity. Bash is known by a $ prompt (sometimes prefixed with other information
about which directory you’re in, or something similar). Zsh uses a % prompt.
There are multitudes of shells, but we’ll just assuming you’re going to use Bash or Zsh (with a hat-tip to
Windows’s built-in shells), and they’re compatible enough for our purposes.
and Python should be there; running this should get you the version number:
python3 --version
1
This is a bit of a pun around the original Bourne Shell from back in the day. Bash improves on it a bit.
2
https://git-scm.com/
3
https://git-scm.com/downloads
4
https://learn.microsoft.com/en-us/windows/wsl/install
19.4. Installing an IDE 209
19.3.5 Mac
Macs come with a terminal built-in. Run the Terminal app and you’ll be presented with a bash shell prompt.
19.3.6 Linux/Unix-likes
All Unix-likes come with a variety of terminals and shells. Google for your distribution.
19.4.2 Mac
Just install it. No special instructions.
If you already have a code editor you prefer using (Vim, Emacs, Sublime, Atom, PyCharm, etc.) feel free to
use that, no problem!
Launch a terminal and bring up a shell. You can use another shell if you want, but I’ll be talking bash/zsh
here.
1. cd means change directory. (A directory is the same as a folder.) On a line by itself, it means “change
to my home directory”.
2. mkdir means make directory. We’re creating a new folder called bgpython.
4. ls -la to get a long directory listing (i.e. all the files in that folder.)
This is showing you all the files you have. Namely, there are two of them: . and ... These mean “this
directory” and “parent directory”, respectively. (You know how folders can be inside other folders? The
outer folder is called the “parent” folder, which is what the parent directory is. If you want to get back to
your home directory from here, you can type cd ...)
You should think of the shell as “being” in a certain directory at any particular time. You can
cd into directories, or cd .. back into the parent, or cd to get to your home directory from
anywhere. It’s like the folder you have open that has focus in a GUI.
(The remaining information on each line tells you the permissions on the file, who owns it, how big it is,
when it was modified, and so on. We can worry about that later.)
Other than those there are no other files. We’ll soon fix that! Let’s add a Python program and run it!
19.7. Launching Your Code Editor 211
But wait–isn’t VS Code a full-fledged IDE? Yes, it is. Another popular editor is Vim:
vim hello.py
But in any case, you’re in the editor and ready to type code.
This is your canvas! This is where the magic happens!
If you get in Vim and have no idea how to get out, hit the ESC key and then type :q! and hit
RETURN—this will exit without saving. If you want to save and exit, hit ESC then type ZZ in
caps.
Vim is a complex editor that is hard to learn. But after you learn it, I maintain it’s the fastest
editor on the planet. I’m using it to type this very sentence right now.
To learn it, I recommend OpenVim’s interactive Vim tutorial and this reference of Vim com-
mands from beginner to expert.
Type the following6 into your editor (the line numbers, below, are for reference only and shouldn’t be typed
in):
1 print("Hello, world!")
2 print("My name's Beej and this is (possibly) my first program!")
Then type this to run it (if python doesn’t work, try python3 or py depending on your system):
python hello.py
6
https://beej.us/guide/bgpython/source/examples/hello.py
212 Chapter 19. Accelerating Beyond IDLE
You just wrote some instructions and the computer carried it out!
Next up: write a Quake III clone!
Okay, so maybe there might be a small number of in between things that I skimmed over, but, as Obi-Wan
Kenobi once said, “You’ve taken your first step into a larger world.”
19.9 Exercises
Remember to use the four problem-solving steps to solve these problems: understand the problem, devise a
plan, carry it out, look back to see what you could have done better.
1. Make another program called dijkstra.py that prints out your three favorite Edsger Dijkstra quotes7 .
19.10 Summary
• Move around the directory hierarchy using the shell
• Edit some source code in an editor
• Run that program from the command line
7
https://en.wikiquote.org/wiki/Edsger_W._Dijkstra
Index
Apollo 13, 6
email to Beej, 2
mirroring, 2
translations, 2
213