Beej's Guide to Python Programming For Beginners (2023)
Beej's Guide to Python Programming For Beginners (2023)
For Beginners
1 Intro 1
1.1 Audience . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 Platform and Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.3 Official Homepage and Books For Sale . . . . . . . . . . . . . . . . . . . . . . . . . 1
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
i
CONTENTS ii
5.10 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
5.11 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
7 Strings 34
7.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.3 What is a String? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
7.4 Creating Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
7.5 Converting Other Types To Strings and Vice Versa . . . . . . . . . . . . . . . . . . . 35
7.6 String Concatenation with + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
7.7 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
7.8 Getting Individual Characters From Strings . . . . . . . . . . . . . . . . . . . . . . . 37
7.9 Slices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
7.10 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
7.11 Interlude: Mutable versus Immutable Types . . . . . . . . . . . . . . . . . . . . . . 38
7.12 for-loops with Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
7.13 String Functions and Methods . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
7.14 Formatted Output with F-Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
7.14.1 .format() Method . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.14.2 % printf Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.15 Chapter Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.16 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
7.17 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
8 Lists 48
8.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.3 What Are Lists? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.4 List Assignments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
8.5 for and Lists—Powerful Stuff . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
8.6 for and enumerate() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
8.7 Midterm: Doubling The Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
8.8 Built-in Functions for Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
8.9 What Good Are They? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
8.10 Midterm Challenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
8.11 Building New Lists, Repeating and Empty . . . . . . . . . . . . . . . . . . . . . . . 57
8.12 List Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
8.13 Lists of Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
8.14 Chapter Project Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
8.15 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
8.16 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
9 Dictionaries 69
9.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
9.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
CONTENTS iii
10 Functions 81
10.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
10.2 Chapter Project Specification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
10.3 What Are Functions? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
10.4 Using Built-In Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
10.5 Arguments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
10.6 Writing Your Own Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
10.7 Multiple Return Values . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.8 What Makes a Good Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
10.9 Positional Arguments versus Keyword Arguments . . . . . . . . . . . . . . . . . . . 87
10.10 Interlude: Evaluation Strategies . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
10.11 The Chapter Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
10.12 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
10.13 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
14 Exceptions 147
14.1 Objective . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
14.2 Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
14.3 Errors in Programs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
14.4 Classic Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
14.5 Error Handling with Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
14.6 Catching Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
14.7 Catching Multiple Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
14.8 Catching Multiple Exceptions II . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
14.9 Getting More Exception Information . . . . . . . . . . . . . . . . . . . . . . . . . . 151
14.10 Catching All Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
14.11 Finally finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
14.12 What Else? else! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
14.13 Exception Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
14.14 Raising Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
14.15 Re-raising Exceptions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
14.16 Project Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
14.17 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
14.18 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
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
Chapter 1. Intro 2
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.
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.
2
http://www.catb.org/~esr/faqs/smart-questions.html
Chapter 1. Intro 3
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 other-
wise, including, without limitation, warranties of title, merchantability, fitness for a particular purpose,
noninfringement, 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
4
Chapter 2. What is Programming, Anyway? 5
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.
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.
7
Chapter 3. What software will I need? 8
If you install it from the official website1 , you need to remember to check the “Add to PATH” box during
the install procedure!
Another option to installing Python on Windows is through WSL. We’ll cover this later.
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 some-
thing 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:
and hit RETURN. This commands Python to output the words “Hello, world!”.
>>> print("Hello, world!")
Hello, world!
1
https://www.python.org/downloads/
2
https://www.python.org/downloads/
Chapter 3. What software will I need? 9
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!
Chapter 4
4.1 Objectives
• Edit some source code in the IDLE editor.
• Run that program.
10
Chapter 4. How do I write a program? 11
{.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”.
Pull down “File→New” and that’ll bring up a blank window.
And let’s enter some code!
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!")
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!
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.”
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 .
4.6 Summary
• Use the problem solving framework!
• Edit some source code in the IDLE editor.
• Run that program from within IDLE.
1
https://beej.us/guide/bgpython/source/examples/hello.py
2
https://en.wikiquote.org/wiki/Edsger_W._Dijkstra
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.
12
Chapter 5. Data and Processing Data 13
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.
Let’s expand the example farther to demonstrate:
1 x = 34 # Variable x is assigned value 34
2 print(x)
3 x = 90 # Variable x is assigned value 90
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.
Chapter 5. Data and Processing Data 14
4 print(x)
5
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:
x = x / 5
x /= 10
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.
Chapter 5. Data and Processing Data 15
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.
Before we begin, x has no value. So represent that in your head as “x has no value; it’s invalid”.
Then the first line runs.
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.
That’s keeping a mental model of computation.
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.
$ python3 inputtest.py
Enter a value: 3490
You entered 3490
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.
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?
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”.
Chapter 5. Data and Processing Data 18
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:
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?
This is the Devising a Plan portion of problem-solving. We’re not going to write code to make this happen.
We’re just going to write an outline of the individual steps the program must describe in a language called
pseudocode (which is English that looks kinda like code).
Then when we’re satisfied it’ll work, we can code it up for realsies.
So stop here and take a moment to consider what the step by step might be to get this done.
Really, take a moment, because I’m about to give spoilers. Thinking about how to solve problems is 80%
of what a software developer gets paid to do, so you might as well practice right now.
What do we know? What tools do we have at our disposal? What resources? How do I put all those
together to solve this problem, like solving a puzzle?
Here’s some pseudocode that would get the job done, it looks like:
read string from keyboard into variable x
convert x to int and store it back in x again
read string from keyboard into variable y
convert y to int and store it back in y again
print the sum of x + y
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
• Decimal numbers?
• Non-numbers, like “goat”?
Try all those things with your program. What happens when you try it? Which ones work and which ones
don’t?
Notice that a big, spewing error message is really the worst case scenario here. And it’s not really that
painful. Don’t be afraid to try to break your code. The computer can handle it. Just run it again.
Later, we’ll learn techniques to catch errors like this so that the program doesn’t bomb out, and prints a
nice message to the user to please try again with valid input, thank you very much.
Notice that when the program crashes, buried in all that output, is the line number the pro-
gram crashed on! Very, very useful! And the last line tells you exactly what Python thinks
went wrong.
The point is, if you’re not sure how something will work, just try it. Experiment! Break things! Fix
things! Again, the computer can absolutely handle it. It’s just a big sandbox for you to play in.
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 eventu-
ally, 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!
What could be better?
• We saw earlier that passing in floating point numbers (with a decimal point) bombed out. It would
be nice if the program would add both floating-point.
What else could we do?
• What about other math operations?
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!
Chapter 5. Data and Processing Data 21
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 . Re-
member ** 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
you can solve for 𝑥 with the quadratic formula:
√
−𝑏± 𝑏2 − 4𝑎𝑐
𝑥=
2𝑎
This all looks terrifying! Can you feel your brain seizing up over it? Deer in the headlights? That’s
OK. This is how developers feel when confronted with a new problem. Really! All of us! But what
we know is that we have a problem solving framework we can use to attack this problem regardless
of how difficult it seems initially.
Remember: Understand, Plan, then Code It Up.
Take a deep breath. Shake off the fear!
You can absolutely do this. It’s not any harder than anything so far! Let’s go!
Your program should plug a, b, and c into the above formula and print out the result value in x.
Make sure 𝑏2 ≥ 4𝑎𝑐 or there won’t be a solution and you’ll get a “domain error” when
you try to take the square root of a negative number. Some test values for 𝑎, 𝑏, and 𝑐
that work: 5, 9, 3, or 20, 140, 60.
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
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
Chapter 5. Data and Processing Data 22
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.”
(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!
17
https://beej.us/guide/bgpython/source/examples/ex_quadratic.py
18
https://en.wikipedia.org/wiki/Scientific_notation
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.
First, Python runs the first line.
Then it goes to the next.
Then it goes to the last.
23
Chapter 6. Flow Control and Looping 24
A B A AND B
0 0 0
2
Ooooo! 1s and 0s! Binary! For just a moment, here, we’re getting a glimpse of the deep workings of the machine.
Chapter 6. Flow Control and Looping 26
A B A AND B
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?
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
3
https://beej.us/guide/bgpython/source/examples/booltest.py
Chapter 6. Flow Control and Looping 27
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.
4
https://beej.us/guide/bgpython/source/examples/ifelse1.py
Chapter 6. Flow Control and Looping 28
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.
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.
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!
5
https://beej.us/guide/bgpython/source/examples/while.py
Chapter 6. Flow Control and Looping 30
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)
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.
Chapter 6. Flow Control and Looping 31
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 `#`.
Hmmm. Looks like it’s printing a hash on each line. The print() function puts a newline at the end of
the line. We need to override that behavior, and there’s an easy way to do it.
12 # Print the line
13 for i in range(x):
14 print("#", end="") # Set the end-of-line character to nothing
15
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:
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
And, while you’re at it, test a bunch of other numbers to make sure it behaves as you’d expect.
Bonus Question: Can you think of another way to draw the line of characters without using an if inside
the loop? (There’s a hint at this footnote7 .) Coding is creative! There’s more than one way to do these
things. Try them and see which you like better.
(Solution8 .)
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.
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.
7
Have two loops!
8
https://beej.us/guide/bgpython/source/examples/hashast.py
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.
34
Chapter 7. Strings 35
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:
x = int("3490") # Integer 3490
y = float("3.14159") # Float 3.14159
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.
Chapter 7. Strings 36
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?
Problem-solving step: Devising a Plan.
Since we can’t concatenate an int to a str, can we turn the int into a str? Sure! New plan: convert
the int to a str with the str() function, and then concatenate it onto the first string with +.
Problem-solving step: Carrying out the Plan.
x = "Hello"
y = str(3490)
print(x + y) # Hello3490
Success!
Chapter 7. Strings 37
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!
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 :.
Chapter 7. Strings 38
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?
We didn’t need the intermediate variable y. We could have simply:
1 x = input("Enter a string of at least 3 characters: ")
2 print(x[1:-1]) # all but the first and last
Also, we’re not actually enforcing the user to enter at least 3 characters. How would we do that? Remem-
ber 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.
the thing a variable refers to—that is, you can assign the variable to refer to something else—but you can’t
change the immutable thing, itself.)
What this means is that any time you do an operation on any of the types, you get a new entity back. Maybe
the old one is kept, or maybe it is forgotten depending on how your code works.
In short, there’s no way to add something to the end of a string. You can take a string and add something
to it to make a completely new string with the new stuff on the end, but it’s a new string. The original is
never modified since it’s immutable.
x = "hello"
y = x + " world"
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
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, dictio-
naries, 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 misunderstand-
ings as we progress.
for c in s:
print("character:", c)
1
We’ll talk about regular expressions, or regexes, later.
Chapter 7. Strings 40
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.
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.
Chapter 7. Strings 41
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.
2
More on lists in upcoming chapters.
3
Spaces, tabs, and newlines.
Chapter 7. Strings 42
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.
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
which outputs:
a number: 1000
a number: 50
a number: 250
to this:
4
https://docs.python.org/3/library/stdtypes.html#string-methods
5
Also called squirrely braces.
Chapter 7. Strings 43
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.
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.
1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
(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.
• 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!]
Chapter 7. Strings 45
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.
Chapter 7. Strings 46
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!
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
Chapter 7. Strings 47
(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
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)
48
Chapter 8. Lists 49
Fun Lists Fact: Most other languages have a different name for lists: they call them “ar-
rays”. 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.
Let’s take a look at a simple example:
x = [10, 3, 7, 9]
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.
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 as-
signment (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.
Remember our good friend the for loop? We used it with range to loop a number of times, and we used
it with strings to loop over each character in the string.
We can also use it with lists2 to do things with each list element in order!
Here’s a simple example that prints all the elements in a list:
x = [11, 55, 33, 99]
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]
for i, v in enumerate(x):
print(f"The element at index {i} has value {v}")
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”.
Chapter 8. Lists 52
after processing and doubling all the even values, it will be:
[1, 4, 3, 8, 5, 12]
if x % 2 == 0:
print("x is even!")
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
4
https://beej.us/guide/bgpython/source/examples/listdouble.py
Chapter 8. Lists 53
12 x[i] = v * 2
13
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
5
Remember that a method is a function that you call on a particular object with the dot (.) operator.
6
https://beej.us/guide/bgpython/source/examples/listops.py
Chapter 8. Lists 54
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
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.
"eggs",
"bacon",
"sausage",
"spam",
"spam"
]
Sometimes we know those lists upfront, and other times we compute them as we go.
for 98 times:
compute the sum of the previous two numbers
append sum to the list
fiblist.py7 :
1 # initialize the list with [0, 1]
2 fib = [0, 1]
3
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):
[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 .
7
https://beej.us/guide/bgpython/source/examples/fiblist.py
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.
Chapter 8. Lists 57
You can multiply a list by a constant value to get a new list repeated that many times.
What?
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.
Chapter 8. Lists 59
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.
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}
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
And, because of that, we’re planning to print the map out a character at at a time so we can decide if we’re
going to draw an @ or the map character.
What would be a more sensible way to store the map rather than a single big string?
Ponder that for a second.
Spoiler alert!
How about a list of strings? One string would be one row of the map. Then we could go through the
single row a character at a time and decide what to print. (Remember we can use array bracket notation
on a string to get single characters out!)
Look at all the techniques we’re using!
• Variables to store player’s current row and column on the map
• A list of strings to store the map
• Iterating through a list with for
• Iterating through strings for for
• Nested for loops
• if conditions to decide what to do with user input
• if conditions to determine if we can move that direction
• Booleans and a while loop to run the game until the user quits
Holy moly! That’s a lot of stuff. But that’s what we do as software developers: we take all we know and
figure out how to put it together into the solution.
And it’s rarely an obvious one. We all have to work hard to come up with the answers.
Problem-solving step: Carrying out the Plan.
Before we start this phase, I want you to notice how much time we’ve spent on the Understand and Plan
phases without writing any code at all. It’s very tempting, especially for junior devs, to want to jump into
the code without spending sufficient time on Understanding and Planning. Unfortunately, this practice
causes one to waste productivity unnecessarily.
You’re not done understanding the problem until you have no more questions about.
You’re not done making a plan until you know how to convert every step of the plan to code.
If you spend enough time understanding and planning, coding almost becomes an afterthought.
And here we are. Let’s take our pseudocode and convert it into Python.
Coming back to simplify the problem, let’s start by just storing and printing the map. No player, no input,
no loop. Let’s just get that working.
First of all, we need to store the map data, so let’s do that:
1 # The map
2
3 map_data = [
4 "#####################",
5 "#...#...............#",
6 "#...#..........#....#",
7 "#...#..........#....#",
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.
Chapter 8. Lists 63
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 "#..............#....#",
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:
Chapter 8. Lists 64
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
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
Chapter 8. Lists 65
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
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 .)
10
https://en.wikipedia.org/wiki/Roguelike
11
Well, not at this point in our learning, anyway.
12
https://en.wikipedia.org/wiki/ANSI_escape_code
13
https://www.youtube.com/watch?v=q3fx6TugN7g
14
https://docs.python.org/3/howto/curses.html
15
https://beej.us/guide/bgpython/source/examples/adv1.py
Chapter 8. Lists 67
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 .)
16
https://beej.us/guide/bgpython/source/examples/ex_refval.py
17
https://beej.us/guide/bgpython/source/examples/ex_listsum.py
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
Chapter 8. Lists 68
6. Using a list comprehension, make a new list from the following that only includes all-uppercase
versions 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
• How to modify and copy the list
• How to create new lists of repeating values
• List comprehensions! Wow!
• Lists of lists!
Lists are a powerful tool to add to our arsenal. We’re going to make heavy use of them as our programs
get more complex.
21
https://beej.us/guide/bgpython/source/examples/ex_listcompcap.py
22
https://beej.us/guide/bgpython/source/examples/ex_listmult.py
Chapter 9
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]
(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 .)
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!
2
Except via Queen Elizabeth II, like the rest of us.
69
Chapter 9. Dictionaries 70
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.
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"]}')
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")
3
https://en.wikipedia.org/wiki/JSON
4
Time complexity enthusiasts will recognize this as 𝑂(1), or constant time.
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.
Chapter 9. Dictionaries 72
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 .
key a has value 30
key b has value 20
key c has value 10
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}")
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.
Chapter 9. Dictionaries 73
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
}
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.
print(d["beej"]) # 3491
That first way is preferred because it’s easier to read, and easy to read code is Happy Code™.
Chapter 9. Dictionaries 75
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"
]
}
}
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.
Problem-solving step: Carrying out the Plan
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 }
Chapter 9. Dictionaries 76
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}')
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!”)
7
That’s Noughts and Crosses, to some of you.
Chapter 9. Dictionaries 77
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.
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.
Chapter 9. Dictionaries 78
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"
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
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",
}
8
It’s not unusual.
9
https://en.wikipedia.org/wiki/Universally_unique_identifier
10
https://beej.us/guide/bgpython/source/examples/familytree.py
Chapter 9. Dictionaries 80
(Solution11 .)
2. For the dictionary in problem 1, print out all the keys and values.
(Solution12 .)
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 dictio-
naries, even! The only limit is your imagination.
What other kinds of data can you store in dictionaries?
11
https://beej.us/guide/bgpython/source/examples/ex_printkeys.py
12
https://beej.us/guide/bgpython/source/examples/ex_printkeysvals.py
13
https://beej.us/guide/bgpython/source/examples/ex_list2dict.py
14
https://beej.us/guide/bgpython/source/examples/ex_list2dictsort.py
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.
𝑑 = √(𝑥0 − 𝑥1 )2 + (𝑦0 − 𝑦1 )2 + (𝑧0 − 𝑧1 )2
For each pair of 3D points, we take the difference in the X coordinates squared, plus the difference in the
Y coordinates squared plus the difference in the Z coordinates squared, and then we take the square root
of that whole thing. And that’s the distance between the two points.
Example output (corresponding to the example input, above):
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
1
https://en.wikipedia.org/wiki/Pythagorean_theorem#Euclidean_distance
81
Chapter 10. Functions 82
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 Your-
self ). 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
2
Purists will point out exceptions to this, like with __add__(), but let’s skip that for now.
3
https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
4
https://docs.python.org/3/library/functions.html
Chapter 10. Functions 83
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.”
“No it isn’t. It’s just contradiction.”
“No it isn’t.”
—Excerpt from Monty Python’s Argument Clinic sketch
Problem-solving step: Understanding the Problem.
When you call a function, the things you put in the parentheses are called the arguments to the function.
Arguments are separated by commas.
Chapter 10. Functions 84
print(2) # 1 argument
print(2, 3) # 2 arguments
print(2, 3, 4) # 3 arguments
print() # 0 arguments
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”.
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))
5
https://www.defcon.org/html/defcon-2/defcon-2.html
6
This happens all the time in development.
Chapter 10. Functions 85
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.
4 print(do_the_math(30))
Compare the two until you are convinced they are equivalent.
def timesdiv10(x):
return x * 10, x / 10
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!
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.
# 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]
7
https://en.wikipedia.org/wiki/Evaluation_strategy
8
https://en.wikipedia.org/wiki/C_(programming_language)
Chapter 10. Functions 89
– 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 technically 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 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:
9
https://en.wikipedia.org/wiki/Fortran
10
https://en.wikipedia.org/wiki/C%2B%2B
Chapter 10. Functions 90
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.
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 implement 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!
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.
Chapter 10. Functions 92
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):
Chapter 10. Functions 93
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
)
Now, sure, we could just throw that equation in the middle of our code, but this is actually ripe to be made
into its own function! If we do that, we can reuse it easily later, should we ever want to. Let’s wrap it up
and add it to our file:
def dist3d(p0, p1):
"""Return the Euclidean distance between 2 3D points."""
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
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
Chapter 10. Functions 95
In mathematical notation, to variables next to each other like 𝑚1 𝑚2 , above, indicate multiplication.
And for 𝑟2 , a simple equivalent is 𝑟 × 𝑟.
So we can convert that whole equation to:
F = G * (m1 * m2) / (r * r)
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
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!)
96
Chapter 11. Classes and Objects 97
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.
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!
What we’re starting to delve into here is the world of Object-Oriented Programming. To discuss every-
thing about it would definitely be information overload, so we’re just going to start with the basics here,
and revisit some of the concepts later.
A bit of terminology here, following up on the starship example from above.
First of all, we’re going to construct new starship objects that hold all the information about a single ship.
When the object is constructed, it is done based on a blueprint. We call this blueprint a class.
So we’re going to define a blueprint for a starship in a class, and then we’re going to build multiple,
different starships based on that class.
Let’s do this as simple as possible to start. It’s not going to be a common way of doing things, but it’s a
place to get your feet wet. We’ll fix it soon.
First of all, we’ll define a new class. Remember, this is just the blueprint for starships—it’s not a starship
itself.
Class names use camel case2 by convention. Let’s define that starship class!
class StarShip:
pass
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:
print(s1) # <__main__.StarShip object at 0x7fa11828c8e0>
This special function is always named __init__() (with the dunders) and is called the constructor.
Let’s add a constructor to our StarShip class that allows us to pass in a ship name when the ship is
constructed:
class StarShip:
def __init__(self, shipname): # The constructor
self.name = shipname
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
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!
3
In fact, I think the choice to use all those underscores in __init__() is one of the few bad design choices in the language.
4
A lot of other languages use the variable name this instead of self.
Chapter 11. Classes and Objects 100
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
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
5
Technically, even the methods are attributes, but we’ll get into the pedantic details another time.
Chapter 11. Classes and Objects 101
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 con-
solidated.
9 self.location[0] = x
10 self.location[1] = y
11 self.location[2] = z
12
13 s0 = StarShip("MCRN Tachi")
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!
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)
Chapter 11. Classes and Objects 103
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
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.")
• getattr() returns the value of an attribute, optionally returning a default value if the attribute
doesn’t exist.
• setattr() sets the value of an attribute, creating it if it doesn’t exist.
This gives you more flexibility in writing your objects, because then you can have optional attributes on
them.
Let’s demo!
class Foo:
pass
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.
7 class Movie:
8 """Holds all the information about a specific movie."""
9 def __init__(self, name, duration, genre):
10 self.name = name
11 self.duration = duration
12 self.genre = genre
13
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.
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
And the line below that is saying, “Append ‘Shaun of the Dead’ to McMenamin’s list of currently showing
movies.”
Let’s do some more. What do each of these lines do?
30 # Tin Pan is showing Shaun of the Dead and Fastest Indian
31 theaters[1].movies.append(movies[1])
32 theaters[1].movies.append(movies[2])
33
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
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.
Chapter 11. Classes and Objects 108
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 .)
Problem-solving step: Looking Back.
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.
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.
7
https://beej.us/guide/bgpython/source/examples/moviesign2.py
8
https://docs.python.org/3/library/datetime.html
Chapter 11. Classes and Objects 109
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")
Next, get player input of either n, s, w, or e, and change location to the room in the specified
direction.
9
https://beej.us/guide/bgpython/source/examples/ex_car.py
10
https://beej.us/guide/bgpython/source/examples/ex_subway.py
Chapter 11. Classes and Objects 110
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.
111
Chapter 12. Importing Modules 112
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.
we’ll end up with an object called sys with attributes that you can access!
Problem-solving step: Devising a Plan
Digging through the documentation for the sys module, we find there’s something called sys.platform
that looks really promising.
Let’s print it!
Problem-solving step: Carrying Out the Plan
import sys # Gets us access to all the sys goodies
print(sys.platform)
python foo.py
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 argu-
ments.
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.
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!
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]
Chapter 12. Importing Modules 116
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.
Not too shabby. What else can we make better?
It’s time to think like a villain. What can you do to this program as a user to break it?
How about passing in a negative year? (Hey, that works!)
What about a negative month?
That crashes with a big ugly stack trace.
We could fix that by checking the values of month and making sure the user-specified 1-12, or print an
error otherwise. Something like this:
if month < 1 or month > 12:
print("Month must be 1-12!")
sys.exit() # call this to stop running the program here!
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 some-
where? By putting the name of the module first, it helps mitigate that ambiguity:
Chapter 12. Importing Modules 118
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.
So… How do we use it? Let’s read the docs again.
{.py} ZipFile.printdir()
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!
Problem-solving step: Looking Back.
Chapter 12. Importing Modules 121
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
probably.
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
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
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
Chapter 12. Importing Modules 122
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:
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
15
https://beej.us/guide/bgpython/source/examples/ex_uuidgen.py
16
https://docs.python.org/3/library/textwrap.html#textwrap.TextWrapper
17
https://beej.us/guide/bgpython/source/examples/ex_wrap.py
Chapter 12. Importing Modules 123
(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.
18
https://docs.python.org/3/library/random.html
19
https://beej.us/guide/bgpython/source/examples/ex_rand1000.py
20
https://docs.python.org/3/library/time.html
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
124
Chapter 13. Reading Files 125
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!
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.
But what if you have a file that’s 200 GB of data? You (probably, as of this writing in 2020) don’t have
that much memory. How can you deal with big files like this?
The answer is to read them a little bit at a time.
With text files like this, a common thing to do is to read them a line at a time. Then you process that line,
and then move on to the next one. This way you only need to have a single line from the file in memory
at once, instead of the whole 200 GB worth.
Let’s use the with statement to open a file, and then read a line at a time.
line_num = 1
with open("wargames.txt") as f:
for line in f:
print(f'{line_num}: {line}')
line_num += 1
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:
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.
The process is similar to reading, except when we open the file, we need to specify that we want to write.
(If we don’t tell open() otherwise, it assumes we’re opening for reading.)
WARNING: if you open an existing file for writing, the contents of that file is instantly lost!
Let’s open a file and write some data to it using the write() method.
with open("newfile.txt", "w") as f:
f.write("Hello, world!\n")
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!
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!
5
https://beej.us/guide/bgpython/source/examples/lines.txt
Chapter 13. Reading Files 132
$ 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
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).
Chapter 13. Reading Files 133
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.
Problem-solving step: Carrying Out the Plan
Firstly, let’s check and see if the user wants to list lines by checking to see if the first letter of the command
is l. If it is, then it’s on.
But there’s an argument after the l, right? The user has to specify which line to start listing from. And
we have to get that into our handle_list() function somehow.
So let’s do two things. Let’s parse those arguments, if any, out of the overall command. We’ll use split()
to break the command apart on spaces, and then we’ll use a slice from [1:] (that is, from the second
element to the end) to get all the arguments.
The result will have any arguments following the command in a list, or an empty list if there were no
arguments.
And then we’ll pass that to our handler function:
# Grab the arguments after the command
args = command.split(" ")[1:]
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
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
Chapter 13. Reading Files 135
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]))
7
https://en.wikipedia.org/wiki/Off-by-one_error
Chapter 13. Reading Files 136
handle_list(args, lines)
File "lineedit.py", line 48, in handle_list
print(f'{i}: {lines[i]}', end="")
IndexError: list index out of range
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:
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
>
Chapter 13. Reading Files 137
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
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.
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:
print("usage: e line_num")
return
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'
Looking at the documentation, there is an append() method for lists, but it only appends onto the end of
the list.
We want ours to be able to do that, but also to be able to put those lines in the middle, or at the top.
Let’s keep looking down the documentation.
There’s insert() to put an object before an index. This seems to work for the beginning and the middle,
but what about appending to the end? Do we have to use the append() method in that special case?
The docs aren’t entirely clear on the matter. Let’s bring up the REPL and try some tests.
I’ll make a list and then try to insert() values in various places.
First, let’s try inserting before element 0, which should insert at the beginning:
>>> a = [11, 22, 33]
>>> a.insert(0, 99)
>>> a
[99, 11, 22, 33]
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.
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
Chapter 13. Reading Files 141
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
Chapter 13. Reading Files 142
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
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]
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)
What’s crazy is that you can use this to write Python programs. Let’s do one!
$ python lineedit.py
> a 0
print("Hello, world!")
print("I wrote this with my own editor!")
.
> l 1
1: print("Hello, world!")
2: print("I wrote this with my own editor!")
> w my_hello.py
> q
$ python3 my_hello.py
Hello, world!
I wrote this with my own editor!
$
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.
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.
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.)
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 .)
11
https://beej.us/guide/bgpython/source/examples/games.csv
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
Chapter 13. Reading Files 146
(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
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!
16
https://beej.us/guide/bgpython/source/examples/wargames.txt
17
https://beej.us/guide/bgpython/source/examples/ex_wc.py
18
https://beej.us/guide/bgpython/source/examples/ex_sort.py
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
147
Chapter 14. Exceptions 148
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.
OK, yes, I admit Stonehenge is broke. Allow me my analogy!
s = 'Bears, beets, Battlestar Galactica'
x = s.find(substring)
if x == -1:
print(f"Couldn't find {substring}")
else:
print(f"Found {substring} at index {x}")
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!'
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.
So for example, here’s a successful run:
Enter a number: 3490
3490000
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:
>>> x, y = input("Enter 2 numbers: ").split()
Enter 2 numbers: 1 2 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
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!")
1
https://docs.python.org/3/library/exceptions.html
Chapter 14. Exceptions 151
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?
Again in the REPL:
>>> int("manfrengensenton")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: '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!")
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 excep-
tions.
try:
1 / 0
except ZeroDivisionError as e: # e is a reference to the exception
print(e)
print(repr(e)) # Print its representation
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 .
try:
x, y = input('Enter two numbers separated by a space: ').split()
x = int(x)
y = int(y)
except:
print(sys.exc_info())
2
https://docs.python.org/3/library/exceptions.html
3
A traceback, also known as a stack trace, is a list of all the function calls that have taken place to get to this point. It’s really
useful information for debugging.
Chapter 14. Exceptions 153
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.
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!
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.
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
Exception 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,
MyAwesomeException is an Exception.”
4
https://docs.python.org/3/library/exceptions.html
Chapter 14. Exceptions 155
# 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 Excep-
tion 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 specific 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?
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 differen-
tiate.
So, hey! We now know how to:
• Catch exceptions
• Create new exceptions
• Raise exceptions
That’s not bad so far!
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'
The big challenge here is how do we provide complete error checking of all user inputs to make sure
everything is sensible?
What are all the things that could go wrong?
Go ahead and make a list on your own, and then you can compare it to the list I have, below.
Spoilers ahead!
Here’s what I can think of happening:
• User doesn’t enter the correct number of command-line arguments.
• User enters a non-number for the second command-line argument.
• User enters a filename that doesn’t exist.
• User enters a non-positive number.
• The file isn’t a regular file (e.g. it’s a directory or other special file).
• The user doesn’t have permission to read the file.
• User enters a number that’s larger than the number of lines in the file.
Some of these you can handle with simple if statements. Others we’ll have to catch with exceptions.
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.
Chapter 14. Exceptions 158
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]
8
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="")
Chapter 14. Exceptions 159
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'
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'
5
https://docs.python.org/3/library/exceptions.html
Chapter 14. Exceptions 160
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
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 .)
6
https://beej.us/guide/bgpython/source/examples/head.py
Chapter 14. Exceptions 161
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
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?
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 subtrac-
tions.
162
Chapter 15. Appendix A: Basic Math for Programmers 163
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.
1
https://www.youtube.com/watch?v=XO0pcWxcROI
Chapter 15. Appendix A: Basic Math for Programmers 164
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.
But their unquenchable thirst for power didn’t end there. Mathematicians also decided that there was such
a thing as negative exponents.
If you have a negative exponent, you need to invert the fraction before applying the exponent. The fol-
lowing is true:
3
−3
1
4 =( )
4
−8 8
3 4
( ) =( )
4 3
And in case you’re wondering how to raise a fraction to an exponent, you just apply the exponent to the
numerator and denominator:
8
4 48 65536
( ) = 8=
3 3 6561
We also have some shorthand names for certain exponents.
𝑛2 we say “𝑛 squared”. Anything raised to the power of 2 is that number squared.
𝑛3 we say “𝑛 cubed”. Anything raised to the third power is that number cubed.
In casual writing, you’ll often see the caret character ^ used to indicate an exponent. So if someone writes
14^4, that’s the same as 144 .
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:
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
Chapter 15. Appendix A: Basic Math for Programmers 167
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.
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.
print(2.7**100) # 1.3689147905858927e+43
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?
With logs, we say “log base x of y”. For example, “log base 2 of 32”, which is written like this:
log2 32
What that is asking is “2 to the what power is 32?” Or, in math:
These are both true:
𝑥 = 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 computa-
tional complexity5 .
4
https://en.wikipedia.org/wiki/Natural_logarithm
5
https://en.wikipedia.org/wiki/Big_O_notation
Chapter 15. Appendix A: Basic Math for Programmers 170
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 𝑒.)
For example, to compute log2 999:
import math
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
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
Chapter 15. Appendix A: Basic Math for Programmers 171
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
numbers.
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
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
numbers, 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
6
https://docs.python.org/3/tutorial/floatingpoint.html#tut-fp-issues
Chapter 15. Appendix A: Basic Math for Programmers 172
round_up(-2.1) # -3
round_up(-2.8) # -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
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
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.)
173
Chapter 16. Appendix B: The REPL 174
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.
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 documen-
tation for the more common functions, like this one:
Chapter 16. Appendix B: The REPL 175
| 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.
Chapter 16. Appendix B: The REPL 176
On Windows and Windows and Windows and Windows and any Windows variant and MS-DOS, EOF is
indicated from the keyboard with CTRL-z. Followed by RETURN.
Additionally, on any system, you can type exit() at the >>> prompt and it’ll quit out.
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.
177
Chapter 17. Appendix C: Assignment Behavior 178
When you call a function, all the arguments passed to the function are assigned into the parameters in the
parameter list.
That assignment, even though it doesn’t use the = assignment operator, behaves in the same way, nonethe-
less.
In the following example, both x and a refer to the same object… right up to the moment we reassign x
on line 4. At that point, x refers to a different list, but a still refers to the original.
1 def foo(x):
2 x[1] = 99 # x still refers to the same list as a
3
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!".
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!
Chapter 17. Appendix C: Assignment Behavior 179
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:
>>> 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.
Chapter 17. Appendix C: Assignment Behavior 180
181
Chapter 18. Appendix D: Number Bases 182
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.
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
184
Chapter 19. Accelerating Beyond IDLE 185
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
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.
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
Chapter 19. Accelerating Beyond IDLE 186
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!
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!
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.
Chapter 19. Accelerating Beyond IDLE 188
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!")
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
6
https://beej.us/guide/bgpython/source/examples/hello.py
7
https://en.wikiquote.org/wiki/Edsger_W._Dijkstra
Index
Apollo 13, 5
email to Beej, 2
mirroring, 2
translations, 2
189