Head First Python PDF
Head First Python PDF
“Head First Python is a great introduction to not just the Python language, but Python as it’s used in the
real world. The book goes beyond the syntax to teach you how to create applications for Android phones,
Google’s App Engine, and more.”
— David Griffiths, author and Agile coach
“Where other books start with theory and progress to examples, Head First Python jumps right in with code
and explains the theory as you read along. This is a much more effective learning environment, because
it engages the reader to do from the very beginning. It was also just a joy to read. It was fun without
being flippant and informative without being condescending. The breadth of examples and explanation
covered the majority of what you’ll use in your job every day. I’ll recommend this book to anyone
starting out on Python.”
— Jeremy Jones, coauthor of Python for Unix and Linux System Administration
“Head First Python is a terrific book for getting a grounding in a language that is increasing in relevance
day by day.”
— Phil Hartley, University of Advancing Technology
Praise for other Head First books
“Kathy and Bert’s Head First Java transforms the printed page into the closest thing to a GUI you’ve ever
seen. In a wry, hip manner, the authors make learning Java an engaging ‘what’re they gonna do next?’
experience.”
— Warren Keuffel, Software Development Magazine
“Beyond the engaging style that drags you forward from know-nothing into exalted Java warrior status, Head
First Java covers a huge amount of practical matters that other texts leave as the dreaded ‘exercise for the
reader.…’ It’s clever, wry, hip and practical—there aren’t a lot of textbooks that can make that claim and live
up to it while also teaching you about object serialization and network launch protocols.”
— Dr. Dan Russell, Director of User Sciences and Experience Research
IBM Almaden Research Center (and teaches Artificial Intelligence at
Stanford University)
“It’s fast, irreverent, fun, and engaging. Be careful—you might actually learn something!”
— Ken Arnold, former Senior Engineer at Sun Microsystems
Coauthor (with James Gosling, creator of Java), The Java Programming
Language
“I feel like a thousand pounds of books have just been lifted off of my head.”
— Ward Cunningham, inventor of the Wiki and founder of the Hillside Group
“Just the right tone for the geeked-out, casual-cool guru coder in all of us. The right reference for practi-
cal development strategies—gets my brain going without having to slog through a bunch of tired, stale
professor-speak.”
— Travis Kalanick, founder of Scour and Red Swoosh
Member of the MIT TR100
“There are books you buy, books you keep, books you keep on your desk, and thanks to O’Reilly and the
Head First crew, there is the penultimate category, Head First books. They’re the ones that are dog-eared,
mangled, and carried everywhere. Head First SQL is at the top of my stack. Heck, even the PDF I have
for review is tattered and torn.”
— Bill Sawyer, ATG Curriculum Manager, Oracle
“This book’s admirable clarity, humor and substantial doses of clever make it the sort of book that helps
even non-programmers think well about problem-solving.”
— Cory Doctorow, co-editor of Boing Boing
Author, Down and Out in the Magic Kingdom
and Someone Comes to Town, Someone Leaves Town
Praise for other Head First books
“I received the book yesterday and started to read it…and I couldn’t stop. This is definitely très ‘cool.’ It
is fun, but they cover a lot of ground and they are right to the point. I’m really impressed.”
— Erich Gamma, IBM Distinguished Engineer, and coauthor of Design Patterns
“One of the funniest and smartest books on software design I’ve ever read.”
— Aaron LaBerge, VP Technology, ESPN.com
“What used to be a long trial and error learning process has now been reduced neatly into an engaging
paperback.”
— Mike Davidson, CEO, Newsvine, Inc.
“Elegant design is at the core of every chapter here, each concept conveyed with equal doses of
pragmatism and wit.”
— Ken Goldstein, Executive Vice President, Disney Online
“I ♥ Head First HTML with CSS & XHTML—it teaches you everything you need to learn in a ‘fun-coated’
format.”
— Sally Applin, UI Designer and Artist
“Usually when reading through a book or article on design patterns, I’d have to occasionally stick myself
in the eye with something just to make sure I was paying attention. Not with this book. Odd as it may
sound, this book makes learning about design patterns fun.
“While other books on design patterns are saying ‘Bueller…Bueller…Bueller…’ this book is on the float
belting out ‘Shake it up, baby!’”
— Eric Wuehler
“I literally love this book. In fact, I kissed this book in front of my wife.”
— Satish Kumar
Other related books from O’Reilly
Learning Python
Programming Python
Python in a Nutshell
Python Cookbook
Python for Unix and Linux System Administration
Paul Barry
Aaron
The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. The Head First series designations,
Head First Python, and related trade dress are trademarks of O’Reilly Media, Inc.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as
trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a trademark
claim, the designations have been printed in caps or initial caps.
While every precaution has been taken in the preparation of this book, the publisher and the author assume no
responsibility for errors or omissions, or for damages resulting from the use of the information contained herein.
TM
ISBN: 978-1-449-38267-4
[M]
I dedicate this book to all those generous people in the Python
community who have helped to make this great little language the
first-rate programming technology it is.
And to those that made learning Python and its technologies just
complex enough that people need a book like this to learn it.
the author
viii
table of contents
ix
table of contents
meet python
1
Everyone loves lists
You’re asking one question: “What makes Python different?”
The short answer is: lots of things. The longer answers starts by stating that there’s
lots that’s familiar, too. Python is a lot like any other general-purpose programming
language, with statements, expressions, operators, functions, modules, methods,
and classes. All the usual stuff, really. And then there’s the other stuff Python provides
that makes the programmer’s life—your life—that little bit easier. You’ll start your tour
of Python by learning about lists. But, before getting to that, there’s another important
question that needs answering…
x
table of contents
2
Modules of functions
Reusable code is great, but a shareable module is better.
By sharing your code as a Python module, you open up your code to the entire Python
community…and it’s always good to share, isn’t it? In this chapter, you’ll learn how to
create, install, and distribute your own shareable modules. You’ll then load your module
onto Python’s software sharing site on the Web, so that everyone can benefit from your
work. Along the way, you’ll pick up a few new tricks relating to Python’s functions, too.
xi
table of contents
3
Dealing with errors
It’s simply not enough to process your list data in your code.
You need to be able to get your data into your programs with ease, too. It’s no surprise
then that Python makes reading data from files easy. Which is great, until you
consider what can go wrong when interacting with data external to your programs…
and there are lots of things waiting to trip you up! When bad stuff happens, you need a
strategy for getting out of trouble, and one such strategy is to deal with any exceptional
situations using Python’s exception handling mechanism showcased in this chapter.
xii
table of contents
persistence
4
Saving data to files
It is truly great to be able to process your file-based data.
But what happens to your data when you’re done? Of course, it’s best to save your
data to a disk file, which allows you to use it again at some later date and time. Taking
your memory-based data and storing it to disk is what persistence is all about. Python
supports all the usual tools for writing to files and also provides some cool facilities for
efficiently storing Python data.
xiii
table of contents
comprehending data
5
Work that data!
Data comes in all shapes and sizes, formats and encodings.
To work effectively with your data, you often have to manipulate and transform it into a
common format to allow for efficient processing, sorting, and storage. In this chapter,
you’ll explore Python goodies that help you work your data up into a sweat, allowing
you to achieve data-munging greatness.
This chapter’s
guaranteed to give you
a workout!
xiv
table of contents
6
Bundling code with data
It’s important to match your data structure choice to your data.
And that choice can make a big difference to the complexity of your code. In Python,
although really useful, lists and sets aren’t the only game in town. The Python dictionary
lets you organize your data for speedy lookup by associating your data with names, not
numbers. And when Python’s built-in data structures don’t quite cut it, the Python class
statement lets you define your own. This chapter shows you how.
The Object
Factory
xv
table of contents
web development
7
Putting it all together
Sooner or later, you’ll want to share your app with lots of people.
You have many options for doing this. Pop your code on PyPI, send out lots of emails, put
your code on a CD or USB, or simply install your app manually on the computers of those
people who need it. Sounds like a lot of work…not to mention boring. Also, what happens
when you produce the next best version of your code? What happens then? How do
you manage the update? Let’s face it: it’s such a pain that you’ll think up really creative
excuses not to. Luckily, you don’t have to do any of this: just create a webapp instead. And,
as this chapter demonstrates, using Python for web development is a breeze.
xvi
table of contents
8
Small devices
Putting your data on the Web opens up all types of possibilities.
Not only can anyone from anywhere interact with your webapp, but they are increasingly
doing so from a collection of diverse computing devices: PCs, laptops, tablets, palmtops,
and even mobile phones. And it’s not just humans interacting with your webapp that
you have to support and worry about: bots are small programs that can automate web
interactions and typically want your data, not your human-friendly HTML. In this chapter,
you exploit Python on Coach Kelly’s mobile phone to write an app that interacts with your
webapp’s data.
xvii
table of contents
9
Handling input
The Web and your phone are not just great ways to display data.
They are also great tools to for accepting input from your users. Of course, once your
webapp accepts data, it needs to put it somewhere, and the choices you make when
deciding what and where this “somewhere” is are often the difference between a webapp
that’s easy to grow and extend and one that isn’t. In this chapter, you’ll extend your
webapp to accept data from the Web (via a browser or from an Android phone), as well
as look at and enhance your back-end data-management services.
xviii
table of contents
10
Getting real
The Web is a great place to host your app…until things get real.
Sooner or later, you’ll hit the jackpot and your webapp will be wildly successful. When
that happens, your webapp goes from a handful of hits a day to thousands, possibly ten
of thousands, or even more. Will you be ready? Will your web server handle the load?
How will you know? What will it cost? Who will pay? Can your data model scale to
millions upon millions of data items without slowing to a crawl? Getting a webapp up and
running is easy with Python and now, thanks to Google App Engine, scaling a Python
webapp is achievable, too.
xix
table of contents
11
Data wrangling
It’s great when you can apply Python to a specific domain area.
Whether it’s web development, database management, or mobile apps, Python helps
you get the job done by not getting in the way of you coding your solution. And then
there’s the other types of problems: the ones you can’t categorize or attach to a domain.
Problems that are in themselves so unique you have to look at them in a different, highly
specific way. Creating bespoke software solutions to these type of problems is an area
where Python excels. In this, your final chapter, you’ll stretch your Python skills to the
limit and solve problems along the way.
What’s a good time goal for the next race? 398
So…what’s the problem? 400
Start with the data 401
Store each time as a dictionary 407
Dissect the prediction code 409
Get input from your user 413
Getting input raises an issue… 414
Search for the closest match 416
The trouble is with time 418
The time-to-seconds-to-time module 419
The trouble is still with time… 422
Port to Android 424
Your Android app is a bunch of dialogs 425
Put your app together… 429
Your app’s a wrap! 431
Your Python Toolbox 432
xx
table of contents
leftovers
i
The Top Ten Things (we didn’t cover)
You’ve come a long way.
But learning about Python is an activity that never stops. The more Python you code,
the more you’ll need to learn new ways to do certain things. You’ll need to master new
tools and new techniques, too. There’s just not enough room in this book to show you
everything you might possibly need to know about Python. So, here’s our list of the top
ten things we didn’t cover that you might want to learn more about next.
xxi
how to use this book
Intro
I can’t believe
they put that in a
Python book.
ning question:
In this section, we answer thein bur
Python book?”
“So why DID they put that a
xxiii
how to use this book
xxiv intro
the intro
t” reader as a learner.
We think of a “Head Firs
then make sure
me th ing ? First, you have to get it,
So what does it take
to lea rn so on the latest
t pu sh ing fac ts int o your head. Based
It’s not abou onal psychology, lea
rning
you don’t forget it. urob iology, and educati
e sc ien ce, ne
research in cognitiv turns your brain on
.
re th an text on a page. We know what
takes a lot mo
ciples:
First lear ning prin
Some of the Head
ch
, and make learning mu
are far mo re me mo rable than words alone ng s mo re
ages also makes thi
Make it visual. Im in rec all an d transfer studies). It
89% impro vem en t r tha n on
more effective (up to relate to, rathe
th in or ne ar the gr aphics they the
understandable. Put
the word s wi ve problems related to
lea rne rs wil l be up to twice as likely to sol
er page, and
the bottom or on anoth
content. rformed up
t studies, students pe
d pe rs on al ized style. In recen erson,
Use a conver satio
na l an reader, using a first-p
ts if the con ten t spoke directly to the language.
to 40% better on post-
learning tes lec turing. Use cas l
ua
a for ma l ton e. Tell stories instead of
conversational style rat
her than tak ing ulating dinner rty
pa
wo uld you pa y mo re attention to: a stim
seriously. Which
Don’t take yourself too
e?
companion or a lec tur neurons,
s you actively flex your
k mo re de ep ly. In other words, unles d ins pired to
Get the lear ner to
thin ged, curious, an
. A rea de r ha s to be motivated, enga
nothing much happen
s in your he ad t, you need challenges,
d ge ne rat e ne w knowledge. And for tha
conclusions, an of the brain and
solve problems, draw s, an d act ivit ies that involve both sides
provoking quest ion
exercises, and thought-
multiple senses. s but I can’t
really want to learn thi
ad er ’s at te nt io n. We’ve all had the “I the ordinary,
Get—and keep—t
he re that are out of
ur bra in pa ys attention to things be
stay awake past page
one” experi en ce. Yo l topic doesn have to
’t
exp ect ed . Lea rni ng a new, tough, technica
-catching, un
interesting, strange, eye if it’s not.
l lea rn much more quick ly
boring. Your brain wil gely dependent
t yo ur ab ilit y to rem ember something is lar
tha something.
ions. We now know ember when you feel
Touch their emot r wh at you care about. You rem
em be
on its emotional conten
t. Yo u rem ’re talking emoti s like
on
sto rie s ab ou t a boy and his dog. We e,
No, we’re not talking
heart‑wren ch ing when you solve a puzzl
” , an d the fee lin g of “I Rule!” that comes hn ica l
, “what the…? t “I’m more tec
surprise, curiosity, fun ha rd, or rea lize yo u know something tha
ody else thinks is
learn something everyb
en eering doesn’t.
gin
than thou” Bob from
xxvi intro
the intro
xxviii intro
the intro
1 Slow down. The more you understand, the 6 Drink water. Lots of it.
less you have to memorize. Your brain works best in a nice bath of fluid.
Don’t just read. Stop and think. When the book asks Dehydration (which can happen before you ever
you a question, don’t just skip to the answer. Imagine feel thirsty) decreases cognitive function.
that someone really is asking the question. The
more deeply you force your brain to think, the better 7 Listen to your brain.
chance you have of learning and remembering. Pay attention to whether your brain is getting
overloaded. If you find yourself starting to skim
2 Do the exercises. Write your own notes. the surface or forget what you just read, it’s time
We put them in, but if we did them for you, that for a break. Once you go past a certain point, you
would be like having someone else do your workouts won’t learn faster by trying to shove more in, and
for you. And don’t just look at the exercises. Use a you might even hurt the process.
pencil. There’s plenty of evidence that physical
activity while learning can increase the learning.
8 Feel something.
Your brain needs to know that this matters. Get
3 Read the “There are No Dumb Questions.”
involved with the stories. Make up your own
That means all of them. They’re not optional captions for the photos. Groaning over a bad joke
sidebars, they’re part of the core content! is still better than feeling nothing at all.
Don’t skip them.
4 Make this the last thing you read before bed. 9 Write a lot of code!
Or at least the last challenging thing. There’s only one way to learn to program: writing
Part of the learning (especially the transfer to a lot of code. And that’s what you’re going to
long-term memory) happens after you put the book do throughout this book. Coding is a skill, and the
down. Your brain needs time on its own, to do more only way to get good at it is to practice. We’re going
processing. If you put in something new during that to give you a lot of practice: every chapter has
processing time, some of what you just learned will exercises that pose a problem for you to solve. Don’t
be lost. just skip over them—a lot of the learning happens
when you solve the exercises. We included a solution
5 Talk about it. Out loud. to each exercise—don’t be afraid to peek at the
Speaking activates a different part of the brain. If solution if you get stuck! (It’s easy to get snagged
you’re trying to understand something, or increase on something small.) But try to solve the problem
your chance of remembering it later, say it out loud. before you look at the solution. And definitely get it
Better still, try to explain it out loud to someone else. working before you move on to the next part of the
You’ll learn more quickly, and you might uncover book.
ideas you hadn’t known were there when you were
reading about it.
Read Me
This is a learning experience, not a reference book. We deliberately stripped out everything
that might get in the way of learning whatever it is we’re working on at that point in the
book. And the first time through, you need to begin at the beginning, because the book
makes assumptions about what you’ve already seen and learned.
xxx intro
the intro
http://www.headfirstlabs.com/books/hfpython/
http://python.itcarlow.ie
Phil Hartley
Technical Reviewers:
David Griffiths is the author of Head First Rails Phil Hartley has a degree in Computer Science
and the coauthor of Head First Programming. He began from Edinburgh, Scotland. Having spent more than
programming at age 12, when he saw a documentary 30 years in the IT industry with specific expertise in
on the work of Seymour Papert. At age 15, he wrote OOP, he is now teaching full time at the University
an implementation of Papert’s computer language of Advancing Technology in Tempe, AZ. In his spare
LOGO. After studying Pure Mathematics at University, time, Phil is a raving NFL fanatic
he began writing code for computers and magazine
Jeremy Jones is coauthor of Python for Unix and
articles for humans. He’s worked as an agile coach,
Linux System Administration. He has been actively using
a developer, and a garage attendant, but not in that
Python since 2001. He has been a developer, system
order. He can write code in over 10 languages and
administrator, quality assurance engineer, and tech
prose in just one, and when not writing, coding, or
support analyst. They all have their rewards and
coaching, he spends much of his spare time traveling
challenges, but his most challenging and rewarding job
with his lovely wife—and fellow Head First author—
has been husband and father.
Dawn.
xxxii intro
the intro
Acknowledgments
My editor:
Brian Sawyer was Head First Python’s editor. When not editing
books, Brian likes to run marathons in his spare time. This turns out
to be the perfect training for working on another book with me (our
second together). O’Reilly and Head First are lucky to have someone
of Brian’s caliber working to make this and other books the best they
can be. Brian Sawyer
The O’Reilly team:
Karen Shaner provided administrative support and very capably coordinated the techical review process, responding
quickly to my many queries and requests for help. There’s also the back-room gang to thank—the O’Reilly Production
Team—who guided this book through its final stages and turned my InDesign files into the beautiful thing you’re
holding in your hands right now (or maybe you’re on an iPad, Android tablet, or reading on your PC—that’s cool, too).
And thanks to the other Head First authors who, via Twitter, offered cheers, suggestions, and encouragement
throughout the entire writing process. You might not think 140 characters make a big difference, but they really do.
I am also grateful to Bert Bates who, together with Kathy Sierra, created this series of books with their wonderful
Head First Java. At the start of this book, Bert took the time to set the tone with a marathon 90-minute phone call,
which stretched my thinking on what I wanted to do to the limit and pushed me to write a better book. Now, some nine
months after the phone call, I’m pretty sure I’ve recovered from the mind-bending Bert put me through.
Friends and colleagues:
My thanks again to Nigel Whyte, Head of Department, Computing and Networking at The Institute of Technology,
Carlow, for supporting my involvement in yet another book (especially so soon after the last one).
My students (those enrolled on 3rd Year Games Development and 4th Year Software Engineering) have been exposed
to this material in various forms over the last 18 months. Their positive reaction to Python and the approach I take with
my classes helped inform the structure and eventual content of this book. (And yes, folks, some of this is on your final).
Family:
My family, Deirdre, Joseph, Aaron, and Aideen had to, once more, bear the grunts and groans, huffs and puffs,
and more than a few roars on more than one occasion (although, to be honest, not as often they did with Head First
Programming). After the last book, I promised I wouldn’t start another one “for a while.” It turned out “a while” was no
more than a few weeks, and I’ll be forever grateful that they didn’t gang up and throw me out of the house for breaking
my promise. Without their support, and especially the ongoing love and support of my wife, Deirdre, this book would
not have seen the light of day.
The without-whom list:
My technical review team did an excellent job of keeping me straight and making sure what I covered was spot on.
They confirmed when my material was working, challenged me when it wasn’t and not only pointed out when stuff
was wrong, but provided suggestions on how to fix it. This is especially true of David Griffiths, my co-conspirator on
Head First Programming, whose technical review comments went above and beyond the call of duty. David’s name might
not be on the cover of this book, but a lot of his ideas and suggestions grace its pages, and I was thrilled and will forever
remain grateful that he approached his role as tech reviewer on Head First Python with such gusto.
xxxiv intro
1 meet python
Before diving head first into Python, let’s get a bit of housekeeping out of
the way.
To work with and execute the Python code in this book, you need a copy of
the Python 3 interpreter on your computer. Like a lot of things to do with
Python, it’s not difficult to install the interpreter. Assuming, of course, it’s not
already there…
2 Chapter 1
meet python
Install Python 3
Before you write and run Python code, you need to make sure the Python
interpreter is on your computer. In this book, you’ll start out with Release 3 of
Python, the very latest (and best) version of the language.
A release of Python might already be on your computer. Mac OS X comes
with Python 2 preinstalled, as do most versions of Linux (which can also ship
with Release 3). Windows, in contrast, doesn’t include any release of Python.
Let’s check your computer for Python 3. Open up a command-line prompt
and, if you are using Mac OS X or Linux, type:
python3 -V
That’s an UP
Do this!
On Windows, use this command:
“v”, by the wayP.ERCASE
When you install Python 3, you also get IDLE, Python’s simple—yet
surprisingly useful— integrated development environment. IDLE includes a
color syntax-highlighting editor, a debugger, the Python Shell, and a complete
copy of Python 3’s online documentation set.
Let’s take a quick look at IDLE.
See results
immediately.
IDLE uses colored syntax to highlight your code. By default, built-in IDLE knows
functions are purple, strings are green, and language keywords (like if) are
orange. Any results produced are in blue. If you hate these color choices, Python’s syntax
don’t worry; you can easily change them by adjusting IDLE’s preferences.
IDLE also knows all about Python’s indentation syntax, which requires code
and helps you
blocks be indented. When you start with Python, this can be hard to get conform to
used to, but IDLE keeps you straight by automatically indenting as needed.
the Python
indentation rules.
4 Chapter 1
meet python
The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
Graham Chapman
The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Life of Brian, 1979, Terry Jones, 94 mins
Graham Chapman
Graham Chapman
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Meaning of Life, 1983, Terry Jones, 107 mins
The six Monty Python cast members
The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
Graham Chapman
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Life of Brian, 1979, Terry Jones, 94 mins
Graham Chapman
The six Monty Python cast members
The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
Graham Chapman
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Life of Brian, 1979, Terry Jones, 94 mins
Graham Chapman
On first glance, this collection of data does indeed look quite complex.
However, the data appears to conform to some sort of structure: there’s a line
for a list of basic movie facts, then another line for the lead actor(s), followed
by a third line listing the movie’s supporting actors.
6 Chapter 1
meet python
To turn the human-friendly list into a Python-friendly one, follow this four-
step process:
1 Convert each of the names into strings by surrounding the data with quotes.
2 Separate each of the list items from the next with a comma.
3 Surround the list of items with opening and closing square brackets.
4 Assign the list to an identifier (movies in the preceding code) using the
assignment operator (=).
It’s perfectly OK to put your list creation code all on one line, assuming, of
course, that you have room:
movies = ["The Holy Grail", "The Life of Brian", "The Meaning of Life"]
8 Chapter 1
meet python
a
Use the “print()” BIF to display No surprise here, really…the
data item on screen. requested data appears on screen
.
Lists in Python might look like arrays, but they are much more than that: they are full-blown Python collection
objects. This means that lists come with ready-to-use functionality in the form of list methods.
Let’s get to know some of Python’s list methods. Open up IDLE and follow along with the code entered at the >>>
prompt. You should see exactly the same output as shown here.
Start by defining a list of names, which you then display on screen using the print() BIF. Then, use the len()
BIF to work out how many data items are in the list, before accessing and displaying the value of the second data
item:
>>> cast = ["Cleese", 'Palin', 'Jones', "Idle"]
>>> print(cast)
['Cleese', 'Palin', 'Jones', 'Idle']
With your list created, you can use list methods to add a single data item to the end of your list (using the
append() method), remove data from the end of your list (with the pop() method), and add a collection of
data items to the end of your list (thanks to the extend() method):
>>> cast.append("Gilliam")
Methods are invoked using the
>>> print(cast)
common “.” dot notation.
['Cleese', 'Palin', 'Jones', 'Idle', 'Gilliam']
>>> cast.pop()
'Gilliam'
ted by commas,
It’s another list: items separats.
surrounded by square bracke
>>> print(cast)
['Cleese', 'Palin', 'Jones', 'Idle']
>>> cast.extend(["Gilliam", "Chapman"])
>>> print(cast)
['Cleese', 'Palin', 'Jones', 'Idle', 'Gilliam', 'Chapman']
Finally, find and remove a specific data item from your list (with the remove() method) and then add a data item
before a specific slot location (using the insert() method):
>>> cast.remove("Chapman")
>>> print(cast)
['Cleese', 'Palin', 'Jones', 'Idle', 'Gilliam'] After all that, we end up with
>>> cast.insert(0, "Chapman") the cast of Monty Python’s
>>> print(cast) Flying Circus!
['Chapman', 'Cleese', 'Palin', 'Jones', 'Idle', 'Gilliam']
10 Chapter 1
meet python
Either strategy works. Which works best for you depends on what you are
trying to do. Let’s recall what the movie buff ’s data looks like:
A number
representing The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
the year is Graham Chapman
next. Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Life of Brian, 1979, Terry Jones, 94 mins
Graham Chapman
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The Meaning of Life, 1983, Terry Jones, 107 mins
The six Monty Python cast members
Graham Chapman, Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
The next piece of data you need to add to your list is a number (which
represents the year the movie was released), and it must be inserted after each
movie name. Let’s do that and see what happens.
12 Chapter 1
meet python
Let’s take a bit of time to try to work out which strategy to use when adding data to your list in
this case.
Given the following list-creation code:
movies = ["The Holy Grail", "The Life of Brian", "The Meaning of Life"]
1 Work out the Python code required to insert the numeric year data into the preceding list,
changing the list so that it ends up looking like this:
["The Holy Grail", 1975, "The Life of Brian", 1979, "The Meaning of Life", 1983]
Write your
insertion
code here.
2 Now write the Python code required to re-create the list with the data you need all in one go:
Write your
re-creation
code here.
In this case, which of these two methods do you think is best? (Circle your choice).
1 or 2
§
Let’s take a bit of time to try and work out which strategy to use when adding data to your list in
this case.
Given the following list-creation code:
movies = ["The Holy Grail", "The Life of Brian", "The Meaning of Life"]
1 You were to work out the Python code required to insert the numeric year data into the preceding
list:
2 You were also to write the Python code required to recreate the list with the data you need all in
one go:
“movies”
"The Life of Brian", 1979,
Assign all your data to the usl ere is
identifier. What was previo y th "The Meaning of Life", 1983]
replaced.
In this case, which of these two methods do you think is best? (You were to circle your choice.)
14 Chapter 1
meet python
e of print(fav_movies[1]) code.
Display the vallulist item
each individua.
on the screen
This code works as expected, making the data from the list appear on screen.
However, if the code is later amended to add another favorite movie to the list,
the list-processing code stops working as expected, because the list-processing code
does not mention the third item.
Big deal: all you need to do is add another print() statement, right?
Yes, adding one extra print() statement works for one extra movie, but
what if you need to add another hundred favorite movies? The scale of the
problem defeats you, because adding all those extra print() statements
becomes such a chore that you would rather find an excuse not to have to do.
Using a for loop scales and works with any size list.
list-processing code
ng code
The list-procesensited
MUST be ind loop.
under the for
The list-processing code is referred to by Python programmers as the suite.
The target identifier is like any other name in your code. As your list is
iterated over, the target identifier is assigned each of the data values in your
list, in turn. This means that each time the loop code executes, the target
identifier refers to a different data value. The loop keeps iterating until it
exhausts all of your list’s data, no matter how big or small your list is.
An alternative to using for is to code the iteration with a while loop.
Consider these two snippets of Python code, which perform the same action:
which requires you while count < len(movies): for each_item in movies:
to employ a counting print(movies[count]) print(each_item)
identifier. count = count+1
16 Chapter 1
meet python
Q: So…when iterating over a list, I Q: Seeing as Python’s lists shrink and Q: What if I need to embed a double
should always use for instead of while? grow as needed, they must not support quote in a string?
A: A:
bounds-checking, right?
Yes, unless you have a really good
reason to use (or need the extra control
of) a while loop. The for loop takes care
A: Well, lists are dynamic, in that they
shrink and grow, but they are not magic,
You have two choices: either escape
the double quote like this: \”, or surround
your string with single quotes.
Q:
of working from the start of your list and in that they cannot access a data item
continuing to the end. It’s next to impossible that does not exist. If you try to access a Can I use any characters to name
to get stung by an off-by-one error when you nonexistent data item, Python responds with my identifiers?
use for. This is not the case with while. an IndexError, which means “out of
Q: bounds.”
A: No. Like most other programming
So, lists aren’t really like arrays
then, because they do so much more? Q: What’s with all the strange
languages, Python has some rules that
must be adhered to when creating names.
A:
references to Monty Python? Names can start with a letter character or
A:
Well…they are in that you can access an underscore, then include any number
individual data items in your list with the Ah, you spotted that, eh? It turns of letter characters, numbers, and/or
standard square bracket notation, but—as out that the creator of Python, Guido van underscores in the rest of the name. Strange
you’ve seen—Python’s lists can do so much Rossum, was reading the scripts of the characters (such as %$£) are not allowed
more. At Head First Labs, we like to think of Monty Python TV shows while designing his and you’ll obviously want to use names that
lists as “arrays on steroids.” new programming language. When Guido have meaning within the context of your
A: No. There are certain enhancements Q: Do I need to know Monty Python in Q: Yes, good naming practice is
to lists that were added in Python 3, but order to understand the examples? always important. But what about case
A:
release 2 of Python has lists, too. All of what sensitivity?
you’ve learned about lists so far will work No, but as they say in the official
with lists in Releases 2 and 3 of Python. Python documentation: “it helps if you do.” A: Yes, Python is the “sensitive type,” in
A:
are surrounded with double quotes and a result of this. For instance, you can use
Lots of programmers are using Python others with single quotes. What’s the an identifier in your code only if it has been
2, but the future of Python development lies difference? given a value; unassigned identifiers cause
A:
with Release 3. Of course, moving the entire a runtime error. This means that if you type
Python community to Python 3 won’t happen There isn’t any. Python lets you use mgs when you meant msg, you’ll find out
overnight, so there’s an awful lot of projects either to create a string. The only rule is that pretty quickly when Python complains about
that will continue to run on Release 2 for the if you start a string with one of the quotes, your code having a NameError.
foreseeable future. Despite 2’s dominance then you have to end it with the same
at the moment, at Head First Labs we think quote; you can’t mix’n’match. As you may
the new bits in 3 are well worth the added have seen, IDLE uses single quotes when
investment in learning about them now. displaying strings within the shell.
Don’t worry: if you know 2, Python 3 is easy.
In Python, you can turn this real list of data into code with little or no effort.
All you need to remember is that every list is a collection of items separated
from each other with commas and surrounded with square brackets. And, of
course, any list item can itself be another list:
Creating a list that contains another list is straightforward. But what happens when you try to process a list that
contains another list (or lists) using the for loop from earlier in this chapter?
Let’s use IDLE to work out what happens. Begin by creating the list of the movie data for “The Holy Grail” in
memory, display it on screen, and then process the list with your for loop:
>>> movies = ["The Holy Grail", 1975, "Terry Jones & Terry Gilliam", 91,
["Graham Chapman", ["Michael Palin", "John Cleese",
"Terry Gilliam", "Eric Idle", "Terry Jones"]]]
>>> print(movies)
['The Holy Grail', 1975, 'Terry Jones & Terry Gilliam', 91, ['Graham Chapman', ['Michael Palin',
'John Cleese', 'Terry Gilliam', 'Eric Idle', 'Terry Jones']]]
>>> for each_item in movies:
The list within a list within
print(each_item)
a list has been created in
memory.
The Holy Grail
1975 The “for” loop prints each item of
Terry Jones & Terry Gilliam the outer loop ONLY.
91
['Graham Chapman', ['Michael Palin', 'John Cleese', 'Terry Gilliam', 'Eric Idle', 'Terry Jones']]
Create a short list and Let’s use the IDLE shell to learn a little about how isinstance() works:
assign it to an identifier.
>>> names = ['Michael', 'Terry']
20 Chapter 1
meet python
Here’s a copy of the current list-processing code. Your task is to rewrite this code using an if
statement and the isinstance() BIF to process a list that displays another list.
Write your
new code
here.
A: A:
Python?
Q: (that’s two leading and trailing underscore is known as “batteries included”: there’s
Over 70! How am I to remember characters, by the way). The shell spits enough included with Python to let you do
that many, let alone find out what they all out a big list. Try it. All those lowercase most things well, without having to rely on
are? words are BIFs. To find out what any BIF code from third parties to get going. As well
does—like input(), for example—type as lots of BIFs, you’ll find that Python’s
A: You don’t have to worry about
remembering. Let Python do it for you.
help(input) at the shell for a
description of the BIFs function.
standard library is rich and packed with
features waiting to be exploited by you.
Here’s a copy of the current list-processing code. Your task was to rewrite this code using an if
statement and the isinstance() BIF to process a list that displays another list.
Let’s use IDLE to see if this code makes a difference to the output displayed on screen:
by
This is a little better, but notlist here
much…there’s another nested
The Holy Grail
ly.
that’s not being processed proper
1975
Terry Jones & Terry Gilliam
91
Graham Chapman
[‘Michael Palin’, ‘John Cleese’, ‘Terry Gilliam’, ‘Eric Idle’, ‘Terry Jones’]
22 Chapter 1
meet python
The Holy Grail, 1975, Terry Jones & Terry Gilliam, 91 mins
Graham Chapman
Michael Palin, John Cleese, Terry Gilliam, Eric Idle & Terry Jones
24 Chapter 1
meet python
Let’s use IDLE once more to test this latest iteration of your code:
d
eeply neste
>>> for each_item in movies: Process a da nested list
list inside enclosing list.
inside an
if isinstance(each_item, list):
for nested_item in each_item:
if isinstance(nested_item, list):
for deeper_item in nested_item:
print(deeper_item)
else:
print(nested_item)
else:
print(each_item)
26 Chapter 1
meet python
Your code now contains a lot of repeated code. It’s also a mess to look at, even
though it works with the movie buff ’s amended data. All that nesting of for
loops is hard to read, and it’s even harder to ensure that the else suites are
associated with the correct if statement.
There has to be a better way…but what to do?
When code repeats in this way, most programmers look for a way to take
the general pattern of the code and turn it into a reusable function. And
Python programmers think this way, too. Creating a reusable function lets
you invoke the function as needed, as opposed to cutting and pasting existing
code.
28 Chapter 1
meet python
de
The function’sencoted under
function code suite
def print_lol(the_list):
for
if
else:
def print_lol(the_list):
Let’s use IDLE one final time to test your new function. Will it work as well as your earlier code?
>>> print_lol(movies)
Invoke the function.
The Holy Grail
1975
Terry Jones & Terry Gilliam
91
It works, too! The recusrive fun
Graham Chapman
produces EXACTLY the same res ction
Michael Palin
the earlier code. ults as
John Cleese
Terry Gilliam
Eric Idle
Terry Jones
30 Chapter 1
meet python
CHAPTER 1
belt and you’ve added some key Run Python 3 from the command line or
CHAPTER 1
- a built-in function.
by commas and surrounded by square
• “BIF” brackets.
• “Suite” - a block of
Python code, which Lists are like arrays on steroids.
is indented to indicate grouping. Lists can be used with BIFs, but also
• “Batteries included
” - a way of support a bunch of list methods.
on comes
referring to the fact that Pythto get Lists can hold any data, and the data can be
with most everything you’ll need of mixed type. Lists can also hold other lists.
going quickly and productively. Lists shrink and grow as needed. All of the
memory used by your data is managed by
IDLE Notes
Python for you.
Python uses indentation to group statements
together.
• The IDLE shell lets you experiment wit
your code as you write it. h len() BIF provides a length of some data
object or count the number of items in a
• Adjust IDLE’s prefe
rences to suit the collection, such as a list.
way you work. The for loop lets you iterate a list and
• Remember: when wo is often more convenient to use that an
rking with the shell,
use Alt-P for Previous and use Alt equivalent while loop.
Next (but use Ctrl if you’re on -N for The if... else... statement lets you make
a Mac). decisions in your code.
isinstance() BIF checks whether
an identifier refers to a data object of some
specified type.
Use def to define a custom function.
32 Chapter 1
2 sharing your code
Modules of functions
I’d love to share...but
how am I supposed
to function without a
module?
The distribution utilities let you share your modules with the world.
Let’s turn your function into a module, then use the distribution utilities to
share your module with the wider Python programming community.
34 Chapter 2
sharing your code
Your code
from
Chapter 1 def print_lol(the_list):
for each_item in the_list:
if isinstance(each_item, list):
print_lol(each_item)
else:
print(each_item)
Let’s call
this file
“nester.py”.
A: The answer to that question really depends on who you ask. However, you
can, of course, use any text editor to create and save your function’s code in a
text file. Something as simple as NotePad on Windows works fine for this, as does
a full-featured editor such as TextMate on Mac OS X. And there’s also full-fledged Do this!
IDEs such as Eclipse on Linux, as well as the classic vi and emacs editors. And,
as you already know, Python comes with IDLE, which also includes a built-in
code editor. It might not be as capable as those other “real” editors, but IDLE is
installed with Python and is essentially guaranteed to be available. For lots of
jobs, IDLE’s edit window is all the editor you’ll ever need when working with your
Python code. Of course, there are other IDEs for Python, too. Check out WingIDE
Go ahead and create a text
for one that specifically targets Python developers. file called nester.py that
contains your function code
from the end of Chapter 1.
ed
PyPI is pronounc
“pie-pie.”
Geek Bits
The Python Package Index (or PyPI for short) provides a
centralized repository for third-party Python modules on the
Internet. When you are ready, you’ll use PyPI to publish your
If you are already familiar with
module and make your code available for use by others. And your
Perl’s CPAN repository, you can
module is ready, but for one important addition. think of PyPI as the Python
equivalent.
What do you think is missing from your module?
36 Chapter 2
sharing your code
def print_lol(the_list):
Add a commention
for your funct
here. for each_item in the_list:
if isinstance(each_item, list)
print_lol(each_item)
else:
print(each_item)
Q: How do I know where the Python Q: Does it matter where I put my Q: Is there any other way to add a
modules are on my computer? nester.py module? comment to Python code?
A: Ask IDLE. Type import sys; A: For now, no. Just be sure to put it
somewhere where you can find it later. In
A: Yes. If you put a “#” symbol anywhere
on a line, everything from that point to the
sys.path (all on one line) into the IDLE
prompt to see the list of locations that your a while, you’ll install your module into your end of the current line is a comment (unless
Python interpreter searches for modules. local copy of Python, so that the interpreter the “#” appears within a triple quote, in
Q:
can find it without you having to remember which case it’s part of that comment). A lot
when you actually put it. of Python programmers use the “#” symbol
Hang on a second. I can use “;” to
Q:
to quickly switch on and off a single line of
put more than one line of code on the
So comments are like a funny- code when testing new functionality.
same line in my Python programs?
looking string surrounded by quotes?
A: Yes, you can. However, I don’t
recommend that you do so. Better to give A: Yes. When a triple-quoted string is
each Python statement its own line; it makes not assigned to a variable, it’s treated like a
your code much easier for you (and others) comment. The comments in your code are
to read. surrounded by three double quotes, but you
could have used single quotes, too.
38 Chapter 2
sharing your code
Now that you’ve added your comments and created a module, let’s test that your code is still working properly.
Rather than typing your function’s code into IDLE’s prompt, bring the nester.py file into IDLE’s edit window,
and then press F5 to run the module’s code:
Nothing appears to happen, other than the Python shell “restarting” and an empty prompt appearing:
What’s happened is that the Python interpreter has reset and the code in your module has executed. The code
defines the function but, other than that, does little else. The interpreter is patiently waiting for you to do
something with your newly defined function, so let’s create a list of lists and invoke the function on it:
1 Begin by creating a folder for your module. Follow along with each of
With the folder created, copy your nester.py module file into the the steps described on these
folder. To keep things simple, let’s call the folder nester: pages. By the time you reach
the end, your module will
have transformed into a
Python distribution.
The “nester.py”
module file.
Import the
“setup” function
from Python’s from distutils.core import setup
distribution utilities. Associate your module’s
setup( metadata with the setup
name = 'nester', function’s arguments.
These are the version = '1.0.0',
setup function’s py_modules = ['nester'],
argument names. author = 'hfpython', These are the
author_email = 'hfpython@headfirstlabs.com',
values Head First
Labs use with
url = 'http://www.headfirstlabs.com', their modules;
description = 'A simple printer of nested lists', your metadata
) will be different.
40 Chapter 2
sharing your code
nester nester
Your code is in A list of files in
this file. your distribution is
in this file.
nester.py MANIFEST
setup.py Here are your
new folders.
Your metadata is build
in this file.
nester.py
dist
This is your
distribution package.
Your code is in nester-1.0.0.tar.gz
this file. nester.py
nester.pyc A “compiled” version
of your code is in
setup.py
this file.
Your metadata is
in this file.
42 Chapter 2
sharing your code
Write a small program that imports your newly created module, defines a small list called “cast,”
and then uses the function provided by your module to display the contents of the list on screen.
Use the following list data (all strings): Palin, Cleese, Idle, Jones, Gilliam, and Chapman.
Open your program in IDLE’s edit window, and then press F5 to execute your code. Describe
what happens in the space below:
You were to write a small program that imports your newly created module, defines a small list
called “cast,” and then uses the function provided by your module to display the contents of the
list on screen. You were to use the following list data (all strings): Palin, Cleese, Idle, Jones,
Gilliam, and Chapman.
import nester
It’s a simple three-
line program. There’s
nothing too difficult cast = ['Palin’, 'Cleese’, 'Idle’, 'Jones’, 'Gilliam’, 'Chapman’]
here.
print_lol(cast)
Open your program in IDLE’s edit window, and then press F5 to execute your code. Describe
what happens in the space below:
But it didn’t work! IDLE gives an error, and the program does not run!
With your program in the IDLE edit window, pressing F5 (or choosing Run Module from the Run menu) does
indeed cause problems:
Your program does not appear to have executed and an error message is reported:
44 Chapter 2
sharing your code
Let’s test this. Staying at the IDLE shell, import your module, create the list, and then try to invoke the function
without a qualifying name. You’re expecting to see an error message:
the name.
print_lol(cast)
NameError: name 'print_lol' is not defined
When you qualify the name of the function with the namespace, things improve dramatically:
>>> nester.print_lol(cast)
Palin
Cleese
Idle
This time, things work as expect
Jones
the list are displayed on screen. ed…the data items in
Gilliam
Chapman
Geek Bits
46 Chapter 2
sharing your code
plete
Click the confirmation link to com
You are now registered with PyPI.
your PyPI registration.
you are here 4 47
register and upload
48 Chapter 2
sharing your code
Q: Q:
a result of your Java technologies compiling
your code.) The Python interpreter is smart
Which is best: plain imports or Is it really necessary for me to
enough to skip the translation phase the next
specific imports? install my modules into my local copy of
time your module is used, because it can
A:
Python? Can’t I just put them in any old
determine when you’ve made changes to
Neither, really. Most programmers folder and import them from there?
the original module code file. If your module
mix and match based on their own personal
preference and taste (although there are
plenty of programmers willing to argue that
A: Yes, it is possible. Just bear in mind
that Python looks for modules in a very
code hasn’t changed, no translation occurs
and the “compiled” code is executed. If your
code has changed, the translation occurs
their preferred way is the “one true way”). specific list of places (recall the import (creating a new pyc file) as needed. The
sys; sys.path trick from earlier upshot of all this is that when Python sees a
Note that the from module import in this chapter). If you put your modules pyc file, it tries to use it because doing so
function form pollutes your current in a folder not listed in Python’s path list, makes everything go much faster.
namespace: names already defined in your chances are the interpreter won’t find
current namespace are overwritten by the
imported names.
them, resulting in ImportErrors. Using the
distribution utilities to build and install your
Q: Cool. So I can just provide my
users with the pyc file?
Q:
module into your local copy of Python avoids
And when I press F5 in IDLE’s edit these types of errors.
A:pyc
Q:
No, don’t do that, because the use of
window, it’s as if the module’s code is
imported with an import statement, right? I noticed the distribution utiliites the file (if found) is primarily a runtime
optimization performed by the interpreter.
A:
created a file called nester.pyc.
Yes, that is essentially what happens. What’s up with that?
Q: So, can I delete the pyc file if I don’t
A:
The code in your edit window is compiled
and executed by Python, and any names need it?
That’s a very good question. When
in the edit window are imported into the
namespace being used by IDLE’s shell. This
the interpreter executes your module code
for the first time, it reads in the code and A: Sure, if you really want to. Just be
aware that you lose any potential runtime
is handy, because it makes it easy to test translates it into an internal bytecode format
functionality with IDLE. But bear in mind that which is ultimately executed. (This idea is optimization.
outside of IDLE, you still need to import your very similar to the way the Java JVM works:
module before you can use its functionality. your Java code is turned into a class file as
Hang on a second.
We really love your code, I kinda like the way it
but is there any chance this works right now. I vote
thing could print the data NOT to change it.
to screen and indent each
nested list whenever one is
found?
50 Chapter 2
sharing your code
52 Chapter 2
sharing your code
Use your pencil to draw a line matching each BIF to the correct description.
The first one is done for you. Once you have all your lines drawn, circle the
BIF you think you need to use in the next version of your function.
SOLUTION
You were to use your pencil to draw a line matching each BIF to the correct
description. Once you had all your lines drawn, you were to circle the BIF
you think you need to use in the next version of your function.
Q: Don’t I need to import the BIFs in Q: I get how range() works, but surely Q: So BIFs are actually good for me?
A:
order to use them in my program? I could just as easily use a while loop to
A:
do the same thing? BIFs exist to make your programming
No. For all intents and purposes, the
BIFs are specifically imported into every
Python program as well as IDLE.
A: Yes, you can, but it’s not as elegant
as using range(). Seriously, though,
experience as straightforward as possible
by providing a collection of functions that
provide common solutions to common
Q: the while equivalent not only requires problems. Since they are included with
So the BIFs must belong to the you to write more code, but it also makes it Python, you are pretty much assured that
__main__ namespace, right? your responsibility to worry about loop state, they have been tested to destruction and
do “exactly what it says on the tin.” You can
A:
whereas range() worries about this for
you. As a general rule, Python programmers depend on the BIFs. Using them gives your
No. They are automatically imported
look for ways to reduce the amount of code program a leg up and makes you look good.
into the __main__ namespace, but the So, yes, the BIFs are good for you!
BIFs have their very own namespace called they need to write and worry about, which
leads to better code robustness, fewer errors,
(wait for it) __builtins__.
and a good night’s sleep.
Now that you know a bit about the range() BIF, amend your function to use range() to
indent any nested lists a specific number of tab-stops.
Hint: To display a TAB character on screen using the print() BIF yet avoid taking a new-line
(which is print()’s default behavior), use this Python code: print("\t", end='').
"""This is the "nester.py" module and it provides one function called print_lol()
which prints lists that may or may not include nested lists."""
Include the name of the extra arg
def print_lol(the_list, ):
ument.
"""This function takes a positional argument called "the_list", which
is any Python list (of - possibly - nested lists). Each data item in the
provided list is (recursively) printed to the screen on it's own line."""
Now that you know a bit about the range() BIF, you were to amend your function to use
range() to indent any nested lists a specific number of tab-stops.
Hint: To display a TAB character on screen using the print() BIF yet avoid taking a new-line
(which is print()’s default behavior), use this Python code: print("\t", end='').
"""This is the "nester.py" module and it provides one function called print_lol()
which prints lists that may or may not include nested lists."""
It’s time to test the new version of your function. Load your module file into IDLE, press F5 to import the function
into IDLE’s namespace, and then invoke the function on your movies list with a second argument:
Invoke your function, being sure to provide
>>> print_lol(movies, 0) a second argument.
The Holy Grail
1975
The data in “movies” starts to appear on screen…
Terry Jones & Terry Gilliam
91
…then all hell breaks loose! Something is not righ
Traceback (most recent call last): t here.
File "<pyshell#2>", line 1, in <module>
print_lol(movies,0)
File "/Users/barryp/HeadFirstPython/chapter2/nester/nester.py", line 14, in print_lol
print_lol(each_item)
Here’s your clue as to
TypeError: print_lol() takes exactly 2 positional arguments (1 given)
what’s gone wrong.
Please wait.
Compiling
your C++
code…
Ah ha! The old “calling
a function before you’ve defined
it yet” trick, eh? I’ll just make a note
in case you define it later at runtime.
You are planning to do that, right?
Please don’t disappoint me, or I’ll give
you an error...
Running your
Python code
right now…
movies = [ "The Holy Grail", 1975, "Terry Jones & Terry Gilliam",
91,["Graham Chapman", ["Michael Palin",
"John Cleese", "Terry Gilliam", "Eric Idle",
"Terry Jones"]]]
h two arguments,
You are invoking the function wit
nester.print_lol(movies, 0) so that’s OK, too.
With the data assigned to the function’s arguments, the function’s code starts The “movies” list is assigned
to execute on each data item contained within the passed-in list: “the_list”, and the value 0 isto
assigned to “level”.
To save space, the
entire comment is def print_lol(the_list, level):
not shown.
"""This function ... """
Process each item in
the list… for each_item in the_list:
…then decide what if isinstance(each_item, list):
If the data item is a
to do next based on list, recursively invoke
whether or not the print_lol(each_item) the function…hang on
data item is a list. else: a second, that doesn’t
look right!?
for tab_stop in range(level):
print("\t", end='')
print(each_item)
58 Chapter 2
sharing your code
if isinstance(each_item, list):
print_lol(each_item)
if isinstance(each_item, list):
print_lol(each_item, level)
if isinstance(each_item, list):
print_lol(each_item, level+1)
of level by 1 each
Simply increment the value you function.
It’s time to perform that update. time you recursively invoke r
Just as you did when you created and uploaded your distribution, invoke the
setup.py program within your distribution folder to perform the upload:
60 Chapter 2
sharing your code
In your rush to release the lates and greatest version of your module, you
forgot about some of your existing users. Recall that not all of your users want
the new nested printing feature. However, by adding the second argument
to print_lol(), you’ve changed your function’s signature, which means
your module has a different API. Anyone using the old API is going to have
problems.
The ideal solution would be to provide both APIs, one which switches on the
new feature and another that doesn’t. Maybe the feature could be optional?
62 Chapter 2
sharing your code
The addition of a
def print_lol(the_list, level=0): default value has
turned “level” into an
OPTIONAL argument.
With the default value for the argument defined, you can now invoke the
function in a number of different ways:
Amend your code to give the level argument a default value of 0 and then load your code into the IDLE editor.
Press F5 to load the code into the shell and then follow along to confirm that the latest version of your function
works as expected. Start be defining a short list of lists and use the function to display the the list on screen:
>>> names = ['John', 'Eric', ['Cleese', 'Idle'], 'Michael', ['Palin']]
>>> print_lol(names, 0)
John
Eric
The standard behavior works
Cleese as expected, with nested
Idle lists indented.
Michael
Palin
Now try to do the same thing without specifiying the second argument. Let’s rely on the default value kicking in:
>>> print_lol(names)
John
ument, the
Eric
Cleese Without specifying the second . arg
Idle default is used and works, too
Michael
Palin
Now specify a value for the second argument and note the change in the function’s behavior:
>>> print_lol(names, 2)
John
Eric Specify an alternative value for
Cleese argument and the indenting sta the second
Idle that level. rt s from
Michael
Palin
One final example provides what looks like a silly value for the second argument. Look what happens:
>>> print_lol(names, -9)
John
tch es OFF the indenting, as thexae
neg ati ve val ue eff ect ively swi ctly
Using a a positive integer. This looks
Eric
“le vel ” is unl ikely to bec om e
Cleese count for sion 1.0.0, right?
Idle like the original output from ver
Michael
Palin
64 Chapter 2
sharing your code
And with the code changes applied, upload this new version of your
distribution to PyPI:
Success! The messages from setup.py confirm that the your latest version
of nester is up on PyPI. Let’s hope this one satisfies all of your users.
Another
version of “nester” has I can’t believe it! My
been released...but its programs were back to running
default behavior might not fine, but now everything is
be what you want. indented. Has this thing
changed again?!?
66 Chapter 2
sharing your code
1 Amend your module one last time to add a third argument to your function. Call your argument
indent and set it initially to the value False—that is, do not switch on indentation by default.
In the body of your function, use the value of indent to control your indentation code.
Note: to save a bit of space, the comments from the module are not shown here. Of course, you
need to make the necessary adjustments to your comments to keep them in sync with your code.
What needs to
for each_item in the_list:
go in here?
if isinstance(each_item, list):
print_lol(each_item, , level+1)
else:
de
Add a line of con
for tab_stop in range(level):
2 With your new code additions in place, provide the edit you would recommend making to the
setup.py program prior to uploading this latest version of your module to PyPI:
3 Provide the command you would use to upload your new distribution to PyPI:
1 You were to amend your module one last time to add a third argument to your function. You were
to call your argument indent and set it initially to the value False—that is, do not switch on
indentation by default. In the body of your function, you were to use the value of indent to
control your indentation code.
the trick.
print("\t", end='')
print(each_item)
A sweet alternative to this “for”
is this code: print("\t" * level, loop
end='').
2 With your new code additions in place, you were to provide the edit you would recommend
making to the setup.py program prior to uploading this latest version of your module to PyPI:
3 You were to provide the command you would use to upload your new distribution to PyPI:
68 Chapter 2
sharing your code
A final test of the functonality should convince you that your module is now working exactly the way you and your
users want it to. Let’s start with the original, default behavior:
And, finally, control where indentation begins by providing a third argument value:
70 Chapter 2
sharing your code
CHAPTER 2
belt and you’ve added some key
Python goodies to your toolbox. A module is a text file that contains Python
code.
The distribution utilities let you turn your
module into a shareable package.
Python Lingo e
The setup.py program provides
• A “namespace” is a
place in Python’s Each module in Python provides its own
need to use the import statementde, you The range() BIF can be used with for to
explicitly. iterate a fixed number of times.
Including end=’’ as a argument to the
print() BIF switches off its automatic
inclusion of a new-line on output.
Arguments to your functions are optional if
you provide them with a default value.
It’s simply not enough to process your list data in your code.
You need to be able to get your data into your programs with ease, too. It’s no surprise
then that Python makes reading data from files easy. Which is great, until you consider
what can go wrong when interacting with data external to your programs…and there
are lots of things waiting to trip you up! When bad stuff happens, you need a strategy for
getting out of trouble, and one such strategy is to deal with any exceptional situations
using Python’s exception handling mechanism showcased in this chapter.
74 Chapter 3
files and exceptions
Your data
open() as individual
lines.
When you use the open() BIF to access your data in a file, an iterator is
created to feed the lines of data from your file to your code one line at a time.
But let’s not get ahead of ourselves. For now, consider the standard open-
process-close code in Python:
Do this!
Start a new IDLE sesson and import the os module to change the current working directory to the folder that
contains your just-downloaded data file:
>>> import os Import “os” from the Standard Library.
>>> os.getcwd() What’s the current working directory?
'/Users/barryp/Documents'
Change to the folder that contains your data file.
>>> os.chdir('../HeadFirstPython/chapter3')
>>> os.getcwd() Confirm you are now in the right place.
'/Users/barryp/HeadFirstPython/chapter3'
Now, open your data file and read the first two lines from the file, displaying them on screen:
>>> data = open('sketch.txt') Open a named file and assign the file to a file object called “data”.
>>> print(data.readline(), end='')
Man: Is this the right room for an argument?
Use the “readline()” method to grab a
>>> print(data.readline(), end='') line from the file, then use the “print()”
Other Man: I've told you once. BIF to display it on screen.
Let’s “rewind” the file back to the start, then use a for statement to process every line in the file:
>>> data.seek(0) Use the “seek()” method to return to the start of the file.
0 And yes, you can use “tell()” with Python’s files, too.
>>> for each_line in data:
This code should look familiar: it’s
iteration using the file’s data as a standard
print(each_line, end='')
input.
Man: Is this the right room for an argument?
Other Man: I've told you once.
Man: No you haven't!
Other Man: Yes I have. Every line of the data is
Man: When? displayed on screen (although
Other Man: Just now. for space reasons, it is
Man: No you didn't! abridged here).
...
Man: (exasperated) Oh, this is futile!!
(pause)
Other Man: No it isn't!
Man: Yes it is!
>>> data.close() Since you are now done with the file, be sure to close it.
76 Chapter 3
files and exceptions
With this format in mind, you can process each line to extract parts of the line
as required. The split() method can help here:
()” method
Invoke the “splhit the “each_line” each_line.split(":") This tells “split()”
associated wit ak the string what to split on.
string and bre is found.
whenever a “:”
Man
Is this the right room for an argument?
The split() method returns a list of strings, which are assigned to a list of
target identifiers. This is known as multiple assignment:
Let’s confirm that you can still process your file while splitting each line. Type the following code into IDLE’s shell:
>>> data = open('sketch.txt') Open the data file.
>>> for each_line in data:
(role, line_spoken) = each_line.split(':') each part from
Process the data, extractinhg par t on screen.
print(role, end='') each line and displaying eac
print(' said: ', end='')
print(line_spoken, end='')
It’s a ValueError, so
that must mean there’s
something wrong with the
data in your file, right?
78 Chapter 3
files and exceptions
M a n : You didn't!
Other Man: I'm telling you, I did!
Man: You did not!
Other Man: Oh I'm sorry, is this a five minute argument, or the full half hour?
Man: Ah! (taking out his wallet and paying) Just the five minutes.
Other Man: Just the five minutes. Thank you.
Other Man: Anyway, I did.
The error occurs AFTER this line
Man: You most certainly did not! of data.
Other Man: Now let's get one thing quite clear: I most definitely told you!
Man: Oh no you didn't!
Other Man: Oh yes I did!
Notice anything?
Notice anything about the next line of data?
The next line of data has two colons, not one. This is enough extra data
to upset the split() method due to the fact that, as your code currently
stands, split()expects to break the line into two parts, assigning each to
role and line_spoken, respectively.
When an extra colon appears in the data, the split() method breaks the Do this!
line into three parts. Your code hasn’t told split() what to do with the third
part, so the Python interpreter raises a ValueError, complains that you
have “too many values,” and terminates. A runtime error has occurred.
The optional argument to split() controls how many breaks occur within
your line of data. By default, the data is broken into as many parts as is
possible. But you need only two parts: the name of the character and the line
he spoke.
If you set this optional argument to 1, your line of data is only ever broken
into two pieces, effectively negating the effect of any extra colon on any line.
Geek Bits
IDLE gives you searchable access to the entire Python
documentation set via its Help ➝ Python Docs menu option (which
will open the docs in your web browser). If all you need to see is the
documentation associated with a single method or function, use
the help() BIF within IDLE’s shell.
80 Chapter 3
files and exceptions
Here’s the code in the IDLE edit window. Note the extra argument to the split() method.
With the edit applied and saved, press F5 (or select Run Module from IDLE’s Run menu) to try out this version of
your code:
Other Man: Now let's get one thing quite clear: I most definitely told you!
Man: Oh no you didn't!
Other Man: Oh yes I did!
Man: Oh no you didn't!
Other Man: Oh yes I did!
Man: Oh look, this isn't an argument!
(pause)
Other Man: Yes it is! What’s this?!? Some of the ta
Man: No it isn’t! the expected format…which da doesn’t conform to
can’t be good.
(pause)
Man: It's just contradiction!
Other Man: No it isn't!
82 Chapter 3
files and exceptions
Joe
Jill
Jill’s suggested approach certainly works: add the extra logic required to work out
whether it’s worth invoking split() on the line of data. All you need to do is
work out how to check the line of data.
Joe’s approach works, too: let the error occur, spot that it has happened, and then
recover from the runtime error…somehow.
Assign a string to the each_line variable that does not contain a colon, and then use the find() method to
try and locate a colon:
>>> each_line = "I tell you, there's no such thing as a flying circus."
>>> each_line.find(':')
-1 The string does NOT contain a colon, so “find()” returns -1 for NOT FOUND.
Press Alt-P twice to recall the line of code that assigns the string to the variable, but this time edit the string to
include a colon, then use the find() method to try to locate the colon:
>>> each_line = "I tell you: there's no such thing as a flying circus."
>>> each_line.find(':')
10
The string DOES contain a colon, so “find()” returns a positive index value.
84 Chapter 3
files and exceptions
Adjust your code to use the extra logic technique demonstrated on the previous page to deal
with lines that don’t contain a colon character.
data.close()
You were to adjust your code to use the extra logic technique to deal with lines that don’t contain
a colon character:
does work. it
for each_line in data:
if not each_line.find(':') == -1:
the (role, line_spoken) = each_line.split(':', 1)
Note the use of hich
“not” keyword, luwe of
print(role, end='')
data.close()
There might be a problem with this code if the format of the data file
changes, which will require changes to the condition.
86 Chapter 3
files and exceptions
Test Drive
Amend your code within IDLE’s edit window, and press F5 to see if it works.
No errors
this time.
Handle exceptions
Have you noticed that when something goes wrong with your code, the
Python interpreter displays a traceback followed by an error message?
The traceback is Python’s way of telling you that something unexpected has
occurred during runtime. In the Python world, runtime errors are called
exceptions.
Whoooah! I don’t
know what to do with this
error, so I’m gonna raise
an exception...this really is
someone else’s problem.
Try the code first. Then deal with errors as they happen.
88 Chapter 3
files and exceptions
Exceptional flow
Python tries
Normal flow your code, but
fails!
Python tries
your code.
Crash!
Your recovery
code executes.
Your exception
It’s all OK, so is handled.
keep going…
try:
Both “try” your code (which might cause a runtime erro
and “except” r)
are Python
keywords.
except:
your error-recovery code
90 Chapter 3
files and exceptions
Study your program and circle the line or lines of code that you
think you need to protect. Then, in the space provided, state why.
data = open('sketch.txt')
data.close()
State your reason
why here.
Q: Something has been bugging me for a while. When the split() method executes, it passes back a list, but the target identifiers
are enclosed in regular brackets, not square brackets, so how is this a list?
A: Well spotted. It turns out that there are two types of list in Python: those that can change (enclosed in square brackets) and those that
cannot be changed once they have been created (enclosed in regular brackets). The latter is an immutable list, more commonly referred
to as a tuple. Think of tuples as the same as a list, except for one thing: once created, the data they hold cannot be changed under any
circumstances. Another way to think about tuples is to consider them to be a constant list. At Head First, we pronounce “tuple” to rhyme with
“couple.” Others pronounce “tuple” to rhyme with “rupal.” There is no clear concensus as to which is correct, so pick one and stick to it.
You were to study your program and circle the line or lines of
code that you think you need to protect. Then, in the space
provided, you were to state why.
data = open('sketch.txt')
These four
print(role, end='')
print(' said: ', end='')
lines of code
print(line_spoken, end='') all need to be
protected.
data.close()
If the call to “split()” fails, you don’t want the three “print()”
statements executing, so it’s best to protect all four lines of the “if”
suite, not just the line of code that calls “split()”.
92 Chapter 3
files and exceptions
data = open('sketch.txt')
Now, no matter what happens when the split() method is invoked, the
try statement catches any and all exceptions and handles them by ignoring the
error with pass.
With your code in the IDLE edit window, press F5 to run it.
94 Chapter 3
files and exceptions
96 Chapter 3
files and exceptions
import os
Check whether
the file exists.
if os.path.exists('sketch.txt'):
data = open('sketch.txt')
data.close()
Inform the else:
user of the
bad news. print('The data file is missing!')
A quick test of the code confirms that this new problem is dealt with properly. With this new version of your code
in IDLE’s edit window, press F5 to confirm all is OK.
Add another
“try” statement. try:
data = open('sketch.txt')
data.close()
Give the user except:
the bad news. print('The data file is missing!')
Another quick test is required, this time with the version of your program that uses exception handling. Press F5 to
give it a spin.
98 Chapter 3
files and exceptions
Let’s ask a simple question about these two versions of your program: What do each
of these programs do?
Grab your pencil. In box 1, write down what you think the
program on the left does. In box 2, write down what you think
the program on the right does.
You were to grab your pencil, then in box 1, write down what you
thought the program on the left does. In box 2, write down what
ually need you thought the program on the right does.
There’s a lot to write, so youionactthan was
more space for your descripte.
provided on the previous pag
1 The code on the right starts by importing the “os” library, and then it uses “path.exists” to
make sure the data file exists, before it attempts to open the data file. Each line from
the file is then processed, but only after it has determined that the line conforms to the
required format by checking first for a single “:” character in the line. If the “:” is found,
the line is processed; otherwise, it’s ignored. When we’re all done, the data file is closed. And
you get a friendly message at the end if the file is not found.
2
The code on the right opens a data file, processes each line in that file, extracts the data of
interest and displays it on screen. The file is closed when done. If any exceptions occur, this
code handles them.
100 Chapter 3
files and exceptions
try:
data = open('sketch.txt')
As your code is currently written, it is too generic. Any runtime error that
occurs is handled by one of the except suites. This is unlikely to be what
you want, because this code has the potential to silently ignore runtime errors.
try:
data = open('sketch.txt')
102 Chapter 3
files and exceptions
CHAPTER 3
belt and you’ve added some key
Python techiques to your toolbox. Use the open() BIF to open a disk file,
creating an iterator that reads data from
the file one line at a time.
The readline() method reads a
go
single line from an opened file.
IDLE Notes
Tuples are immutable.
A ValueError occurs when your data
does not conform to an expected format.
• Access Python
by choosing Pyth’s documentation An IOError occurs when your data
IDLE’s Help men on Docs from cannot be accessed properly (e.g.,
documentation seu. The Python 3 perhaps your data file has been moved or
your favorite webt should open in renamed).
browser. The help() BIF provides access to
Python’s documentation within the IDLE
shell.
The find() method locates a specific
substring within another string.
The not keyword negates a condition.
The try/except statement provides
an exception-handling mechanism,
allowing you to protect lines of code that
might result in a runtime error.
The pass statement is Python’s empty
or null statement; it does nothing.
Before you learn what’s involved in writing data to disk, let’s process the data
from the previous chapter to work out who said what to whom.
106 Chapter 4
persistence
Code Magnets
Add the code magnets at the bottom of this page to your existing
code to satisfy the following requirements:
try:
data = open('sketch.txt')
for each_line in data:
try:
(role, line_spoken) = each_line.split(':', 1)
except ValueError:
pass
data.close()
elif role
== 'Other
Man':
man = []
if role == 'Man':
other.app
end(line_
line_spoken = line_spoken.strip() other = [] spoken)
man.appen print(man)
d(line_sp
print(other) oken)
try:
data = open('sketch.txt')
for each_line in data:
try:
man.append(line_spoken)
except ValueError:
pass
data.close()
except IOError:
print('The datafile is missing!')
print(man)
Conclude by displaying the
print(other) processed data on screen.
108 Chapter 4
persistence
Test Drive
Load your code into IDLE’s edit window and take it for a spin by pressing F5. Be sure to save your program into the
same folder that contains sketch.txt.
It worked, as expected.
Yes, it can.
Geek Bits
When you use access mode w, Python opens your named file
for writing. If the file already exists, it is cleared of its contents, or
clobbered. To append to a file, use access mode a, and to open a
file for writing and reading (without clobbering), use w+. If you
try to open a file for writing that does not already exist, it is first
created for you, and then opened for writing.
110 Chapter 4
persistence
Call your disk files man_data.txt (for what the man said) and
other_data.txt (for what the other man said). Be sure to
both open and close your data files, as well as protect your code
man = [] against an IOError using try/except.
other = []
try:
data = open('sketch.txt')
for each_line in data:
try:
(role, line_spoken) = each_line.split(':', 1)
line_spoken = line_spoken.strip()
if role == 'Man':
man.append(line_spoken)
elif role == 'Other Man':
other.append(line_spoken)
except ValueError:
pass
data.close()
except IOError:
print('The datafile is missing!')
You were to call your disk files man_data.txt (for what the
man said) and other_data.txt (for what the other man said).
You were to make sure to both open and close your data files, as
man = [] well as protect your code against an IOError using try/except.
other = []
try:
data = open('sketch.txt')
for each_line in data:
try:
(role, line_spoken) = each_line.split(':', 1)
line_spoken = line_spoken.strip()
if role == 'Man': All of this code is
man.append(line_spoken) unchanged.
elif role == 'Other Man':
other.append(line_spoken)
except ValueError:
pass
data.close()
except IOError:
112 Chapter 4
persistence
Test Drive
Perform the edits to your code to replace your two print() calls with your new file I/O code. Then, run your
program to confirm that the data files are created:
That code worked, too. You’ve created two data files, each holding the data
from each of your lists. Go ahead and open these files in your favorite editor
to confirm that they contain the data you expect.
try: Ok.
man_file = open('man_data.txt', 'w') OK
other_file = open('other_data.txt', 'w') OK
print(man, file=man_file) OK
print(other, file=other_file) Not OK!!
Crash!
Your exception-handling code is doing its job, but you now have a situation
where your data could potentially be corrupted, which can’t be good.
What’s needed here is something that lets you run some code regardless of
whether an IOError has occured. In the context of your code, you’ll want
to make sure the files are closed no matter what.
114 Chapter 4
persistence
try:
man_file = open('man_data.txt', 'w')
other_file = open('other_data.txt', 'w')
No changes
here, except
that… print(man, file=man_file)
print(other, file=other_file)
except IOError:
print('File error.')
If no runtime errors occur, any code in the finally suite executes. Equally,
if an IOError occurs, the except suite executes and then the finally
suite runs.
No matter what, the code in the finally suite always runs.
By moving your file closing code into your finally suite, you are reducing
the possibility of data corruption errors.
This is a big improvement, because you’re now ensuring that files are closed
properly (even when write errors occur).
But what about those errors?
Q: I’m intrigued. When you stripped the line_spoken data Q: But surely Python can work out how many variables are
of unwanted whitespace, you assigned the result back to the referring to any one particular string?
A:
line_spoken variable. Surely invoking the strip() method on
line_spoken changed the string it refers to? It does, but only for the purposes of garbage collection. If you
A:
string, converts it to uppercase, and returns it to you. You can then
assign the uppercase data back to the variable that used to refer to Don’t worry: you’ll know. If you try to change an immutable
the original data. value, Python raises a TypeError exception.
Q: And the original data cannot change, because there’s Q: Of course: an exception occurs. They’re everywhere in
another variable referring to it? Python, aren’t they?
116 Chapter 4
persistence
Who knows?
It turns out that the Python interpreter knows…and it will give up the details
if only you’d ask.
When an error occurs at runtime, Python raises an exception of the specific
type (such as IOError, ValueError, and so on). Additionally, Python
creates an exception object that is passed as an argument to your except
suite.
Let’s see what happens when you try to open a file that doesn’t exist, such as a disk file called missing.txt.
Enter the following code at IDLE’s shell:
>>> try:
data = open('missing.txt')
print(data.readline(), end='')
except IOError:
print('File error')
finally:
data.close()
File error
There’s your error message, but…
Traceback (most recent call last):
File "<pyshell#8>", line 7, in <module> …what’s this?!? Another except
data.close() raised and it killed your code. ion was
NameError: name 'data' is not defined
As the file doesn’t exist, the data file object wasn’t created, which subsequently makes it impossible to call the
close() method on it, so you end up with a NameError. A quick fix is to add a small test to the finally
suite to see if the data name exists before you try to call close(). The locals() BIF returns a collection of
names defined in the current scope. Let’s exploit this BIF to only invoke close() when it is safe to do so:
The “in” operator tests This is just the bit of code that
finally: for membership. needs to change. Press Alt-P
if 'data' in locals(): edit your code at IDLE’s shell.to
data.close()
File error
No extra exceptions this time.
Just your error message.
Here you’re searching the collection returned by the locals() BIF for the string data. If you find it, you can
assume the file was opened successfully and safely call the close() method.
If some other error occurs (perhaps something awful happens when your code calls the print() BIF), your
exception-handling code catches the error, displays your “File error” message and, finally, closes any opened file.
But you still are none the wiser as to what actually caused the error.
118 Chapter 4
persistence
When an exception is raised and handled by your except suite, the Python interpreter passes an exception object
into the suite. A small change makes this exception object available to your code as an identifier:
But when you try to run your code with this change made, another exception is raised:
This time your error message didn’t appear at all. It turns out exception objects and strings are not compatible
types, so trying to concatenate one with the other leads to problems. You can convert (or cast) one to the other
using the str() BIF:
try:
with open('its.txt', "w") as data:
print("It's...", file=data)
except IOError as err:
The use of “with” print('File error: ' + str(err))
negates the need for
the “finally” suite.
When you use with, you no longer have to worry about closing any opened
files, as the Python interpreter automatically takes care of this for you. The
with code on the the right is identical in function to that on the left. At Head
First Labs, we know which approach we prefer.
Geek Bits
120 Chapter 4
persistence
try:
man_file = open('man_data.txt', 'w')
other_file = open('other_data.txt', 'w')
print(man, file=man_file)
print(other, file=other_file)
except IOError as err:
print('File error: ' + str(err))
finally:
if 'man_file' in locals():
man_file.close()
if 'other_file' in locals():
other_file.close()
Write your
“with” code
here.
try:
man_file = open('man_data.txt', 'w')
other_file = open('other_data.txt', 'w')
print(man, file=man_file)
print(other, file=other_file)
except IOError as err:
print('File error: ' + str(err))
finally:
if 'man_file' in locals():
man_file.close()
if 'other_file' in locals():
other_file.close()
try:
with open(‘man_data.txt', ‘w') as man_file:
Using two “with”
statements to rewrite print(man, file=man_file)
the code without the with open(‘other_data.txt', ‘w') as other_file:
“finally” suite.
print(other, file=other_file)
except IOError as err:
print(‘File error: ' + str(err))
ls into one
Or combine the two “open()” cal Note the use of the comma.
“with” statement.
with open('man_data.txt', 'w') as man_file, open('other_data.txt’, 'w’) as other_file:
print(man, file=man_file)
print(other, file=other_file)
122 Chapter 4
persistence
Test Drive
Add your with code to your program, and let’s confirm that it continues to function as expected. Delete the two
data files you created with the previous version of your program and then load your newest code into IDLE and give
it a spin.
IDLE
No errors in theindicate
shell appears toram ran
that the prog
successfully.
If you check your folder, your two data files should’ve reappeared. Let’s take a closer look at the data file’s contents
by opening them in your favorite text editor (or use IDLE).
You’ve saved the lists in two files containing what the Man said and what the Other
man said. Your code is smart enough to handle any exceptions that Python or
your operating system might throw at it.
Use a with statement to open your data file and display a single line from it:
Geek Bits
124 Chapter 4
persistence
Parsing the data in the file is a possibility…although it’s complicated by all those
square brackets, quotes, and commas. Writing the required code is doable,
but it is a lot of code just to read back in your saved data.
Of course, if the data is in a more easily parseable format, the task would likely be
easier, so maybe the second option is worth considering, too?
r
This code currently displays you
data on the screen.
Amending this code to print to a disk file instead of the screen (known as
standard output) should be relatively straightforward. You can then save your
data in a more usable format.
Standard Output The default place where your code writes its
data when the “print()” BIF is used. This is typically the screen.
In Python, standard output is referred to as “sys.stdout” and
is importable from the Standard Library’s “sys” module.
126 Chapter 4
persistence
1 Let’s add a fourth argument to your print_lol() function to identify a place to write your
data to. Be sure to give your argument a default value of sys.stdout, so that it continues to
write to the screen if no file object is specified when the function is invoked.
Fill in the blanks with the details of your new argument. (Note: to save on space, the comments
have been removed from this cod, but be sure to update your comments in your nester.py
module after you’ve amended your code.)
2 What needs to happen to the code in your with statement now that your amended print_lol()
function is available to you?
3 List the name of the module(s) that you now need to import into your program in order to support your
amendments to print_lol().
1 You were to add a fourth argument to your print_lol() function to identify a place to write
your data to, being sure to give your argument a default value of sys.stdout so that it
continues to write to the screen if no file object is specified when the function is invoked.
You were to fill in the blanks with the details of your new argument. (Note: to save on space, the
comments have been removed from this code, but be sure to update those in your nester.py
module after you’ve amended your code).
e it a
Add the fourth argument and giv
default value.
def print_lol(the_list, indent=False, level=0, fh=sys.stdout ):
2 What needs to happen to the code in your with statement now that your amended print_lol()
function is available to you?
3 List the name of the module(s) that you now need to import into your program in order to support your
amendments to print_lol().
128 Chapter 4
persistence
Test Drive
Before taking your code for a test drive, you need to do the following:
1. Make the necessary changes to nester and install the amended module into your Python
environment (see Chapter 2 for a refresher on this). You might want to upload to PyPI, too.
2. Amend your program so that it imports nester and uses print_lol() instead of print()
within your with statement. Note: your print_lol() invocation should look something like this:
print_lol(man, fh=man_file).
When you are ready, take your latest program for a test drive and let’s see what happens:
As before, there’s no
output on screen.
Let’s check the contents of the files to see what they look like now.
But does this make it any easier to read the data back in?
130 Chapter 4
persistence
Head First: Hello, CC, how are you today? Custom Code: What?!? That’s where I excel:
creating beautifully crafted custom solutions for folks
Custom Code: Hi, I’m great! And when I’m not
with complex computing problems.
great, there’s always something I can do to fix things.
Nothing’s too much trouble for me. Here: have a Head First: But if something’s been done before,
seat. why reinvent the wheel?
Head First: Why, thanks. Custom Code: But everything I do is custom-
made; that’s why people come to me…
Custom Code: Let me get that for you. It’s my
new custom SlideBack&Groove™, the 2011 model, Head First: Yes, but if you take advantage of other
with added cushions and lumbar support…and it coders’ work, you can build your own stuff in half
automatically adjusts to your body shape, too. How the time with less code. You can’t beat that, can you?
does that feel?
Custom Code: “Take advantage”…isn’t that like
Head First: Actually [relaxes], that feels kinda exploitation?
groovy.
Head First: More like collaboration, sharing,
Custom Code: See? Nothing’s too much trouble participation, and working together.
for me. I’m your “go-to guy.” Just ask; absolutely
Custom Code: [shocked] You want me to give my
anything’s possible when it’s a custom job.
code…away?
Head First: Which brings me to why I’m here. I
Head First: Well…more like share and share alike.
have a “delicate” question to ask you.
I’ll scratch your back if you scratch mine. How does
Custom Code: Go ahead, shoot. I can take it. that sound?
Head First: When is custom code appropriate? Custom Code: That sounds disgusting.
Custom Code: Isn’t it obvious? It’s always Head First: Very droll [laughs]. All I’m saying is
appropriate. that it is not always a good idea to create everything
from scratch with custom code when a good enough
Head First: Even when it leads to problems down
solution to the problem might already exist.
the road?
Custom Code: I guess so…although it won’t be as
Custom Code: Problems?!? But I’ve already told
perfect a fit as that chair.
you: nothing’s too much trouble for me. I live to
customize. If it’s broken, I fix it. Head First: But I will be able to sit on it!
Head First: Even when a readymade solution Custom Code: [laughs] You should talk to my
might be a better fit? buddy Pickle…he’s forever going on about stuff like
this. And to make matters worse, he lives in a library.
Custom Code: Readymade? You mean (I hate to
say it): off the shelf? Head First: I think I’ll give him a shout. Thanks!
Head First: Yes. Especially when it comes to Custom Code: Just remember: you know where to
writing complex programs, right? find me if you need any custom work done.
Just the five minutes.', 'You ‘Ah! (taking out his wallet
and paying) Just the five
minutes.’, ‘You most
certainly did not!’, “Oh
You can, for example, store your pickled data on disk, put it in a database,
or transfer it over a network to another computer.
When you are ready, reversing this process unpickles your persistent pickled
data and recreates your data in its original form within Python’s memory: Your data is recreated
in Python’s memory,
The same pickle engine exactly as before.
Your
pickled ['Is this the right room for an
argument?', "No you haven't!",
data 'When?', "No you didn't!", "You
didn't!", 'You did not!', 'Ah!
[‘Is this the right room
for an argument?’, “No
you haven’t!”, ‘When?’,
(taking out his wallet and paying)
Just the five minutes.', 'You
“No you didn’t!”, “You
didn’t!”, ‘You did not!’,
‘Ah! (taking out his wallet
and paying) Just the five
minutes.’, ‘You most
certainly did not!’, “Oh
no you didn’t!”, “Oh no
you didn’t!”, “Oh look,
this isn’t an argument!”,
most certainly did not!', "Oh
no you didn't!", "Oh no you
“No it isn’t!”, “It’s
just contradiction!”,
‘It IS!’, ‘You just
contradicted me!’, ‘You
DID!’, ‘You did just
132 Chapter 4
persistence
try:
with open('man_data.txt', 'w') as man_file, open('other_data.txt', 'w') as other_file:
nester.print_lol(man, fh=man_file)
nester.print_lol(other, fh=other_file)
except IOError as err:
print('File error: ' + str(err))
rint_lol()”
Replace the two calls to “nester.p
print('File error: ' + str(err))
Q: When you invoked print_lol() earlier, you provided only two arguments, even though the function signature requires you to
provide four. How is this possible?
A: When you invoke a Python function in your code, you have options, especially when the function provides default values for some
arguments. If you use positional arguments, the position of the argument in your function invocation dictates what data is assigned to which
argument. When the function has arguments that also provide default values, you do not need to always worry about positional arguments
being assigned values.
A: Consider print(), which has this signature: print(value, sep=' ', end='\n', file=sys.stdout). By
default, this BIF displays to standard output (the screen), because it has an argument called file with a default value of sys.stdout.
The file argument is the fourth positional argument. However, when you want to send data to something other than the screen, you do not need
to (nor want to have to) include values for the second and third positional arguments. They have default values anyway, so you need to provide
values for them only if the defaults are not what you want. If all you want to do is to send data to a file, you invoke the print() BIF like this:
print("Dead Parrot Sketch", file='myfavmonty.txt') and the fourth positional argument uses the value
you specify, while the other positional arguments use their defaults. In Python, not only do the BIFs work this way, but your custom functions
support this mechamism, too.
134 Chapter 4
persistence
Test Drive
Let’s see what happens now that your code has been amended to use the standard pickle module instead of
your custom nester module. Load your amended code into IDLE and press F5 to run it.
So, once again, let’s check the contents of the files to see what they look like now:
It appears to have worked…but these files look like gobbledygook! What gives?
Recall that Python, not you, is pickling your data. To do so efficiently, Python’s
pickle module uses a custom binary format (known as its protocol). As you
can see, viewing this format in your editor looks decidedly weird.
pickle really shines when you load some previously pickled data into another program. And, of course, there’s
nothing to stop you from using pickle with nester. After all, each module is designed to serve different
purposes. Let’s demonstrate with a handful of lines of code within IDLE’s shell. Start by importing any required
modules:
>>> import pickle
>>> import nester
Next up: create a new identifier to hold the data that you plan to unpickle.Create an empty list called new_man:
>>> new_man = []
Yes, almost too exciting for words, isn’t it? With your list created. let’s load your pickled data into it. As you are
working with external data files, it’s best if you enclose your code with try/except:
>>> try:
with open('man_data.txt', 'rb') as man_file:
new_man = pickle.load(man_file)
except IOError as err:
print('File error: ' + str(err))
except pickle.PickleError as perr:
print('Pickling error: ' + str(perr))
This code is not news to you either. However, at this point, your data has been unpickled and assigned to the
new_man list. It’s time for nester to do its stuff:
>>> nester.print_lol(new_man)
Is this the right room for an argument?
No you haven’t!
Not all the data is shown
When?
here, but trust us: it’s all
No you didn’t!
there.
...
You did just then!
(exasperated) Oh, this is futile!!
Yes it is!
And to finish off, let’s display the first line spoken as well as the last:
>>> print(new_man[0])
Is this the right room for an argument?
>>> print(new_man[-1])
Yes it is!
Python takes care of your file I/O details, so you can concentrate on what
your code actually does or needs to do.
As you’ve seen, being able to work with, save, and restore data in lists is a
breeze, thanks to Python. But what other data structures does Python
support out of the box?
CHAPTER 4
belt and you’ve added some key
CHAPTER 4
go
except suite and can be assigned to
138 Chapter 4
5 comprehending data
The coach is an old friend, and you’d love to help. His crack squad of U10
athletes has been training hard. With each 600m run they do, Coach Kelly
has recorded their time in a text file on his computer. There are four files in james.txt
all, one each for James, Sarah, Julie, and Mikey.
julie.txt 2-34,3:21,2.34,2.45,3.01,2:01,2:01,3:10,2-22
2.59,2.11,2:11,2:23,3-10,2-23,3:10,3.21,3-21
2:22,3.01,3:01,3.02,3:02,3.02,3:22,2.49,2:38
2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55
sarah.txt mikey.txt
Initially, the coach needs a quick way to know the top three fastest times for each
athlete.
Can you help?
140 Chapter 5
comprehending data
Do this!
Let’s begin by reading the data from each of the files into its own list. Write a short program to
process each file, creating a list for each athlete’s data, and display the lists on screen.
Hint: Try splitting the data on the commas, and don’t forget to strip any unwanted whitespace.
Write your
code here.
Let’s begin by reading the data from each of the files into its own list. You were to write a short
program to process each file, creating a list for each athlete’s data. You were then to display the
lists on screen. Open the file.
with open(‘james.txt’) as jaf:
data = jaf.readline() Read the line of data.
james = data.strip().split(‘,’) Convert the data to a list.
with open(‘julie.txt’) as juf:
data = juf.readline()
Open each of
the data files julie = data.strip().split(‘,’)
in turn, read
the line of with open(‘mikey.txt’) as mif:
data from the
file, and create data = mif.readline()
a list from the mikey = data.strip().split(‘,’)
line of data.
with open(‘sarah.txt’) as saf:
data = saf.readline()
sarah = data.strip().split(‘,’)
print(james)
print(julie)
print(mikey) Display the four lists on screen.
print(sarah)
Q: That data.strip().split(',') line looks a little weird. Can you explain what’s going on?
A: That’s called method chaining. The first method, strip(), is applied to the line in data, which removes any unwanted whitespace
from the string. Then, the results of the stripping are processed by the second method, split(','), creating a list. The resulting list is
then applied to the target identifier in the previous code. In this way, the methods are chained together to produce the required result. It helps
if you read method chains from left to right.
142 Chapter 5
comprehending data
Test Drive
Load your code into IDLE and run it to confirm that it’s all OK for now:
Here’s your
program as
displayed in
IDLE.
So far, so good. Coach Kelly’s data is now represented by four lists in Python’s
memory. Other than the use of method chaining, there’s nothing much new
here, because you’ve pretty much mastered reading data from files and using
it to populate lists.
There’s nothing to show the coach yet, so no point in disturbing him until his
data is arranged in ascending order, which requires you to sort it.
Copied sorting takes your data, arranges it in the order you specify, and
then returns a sorted copy of your original data. Your original data’s ordering
is maintained and only the copy is sorted. In Python, the sorted() BIF
supports copied sorting.
The original, a
unordered dat [ 1, 3
, 4, 2
, 6, 5
] ]
6, 5
2,
, 4, The original, unordered data
, 3 remains UNTOUCHED.
[ 1
]
4 , 5, 6
2, 3,
[ 1,
a has now been
The dat pied).
ordered (and co
144 Chapter 5
comprehending data
Let’s see what happens to your data when each of Python’s sorting options is used. Start by creating an unordered
list at the IDLE shell:
et.
The data’s ordering has been res
>>> data
[6, 3, 1, 2, 4, 5]
>>> data2 = sorted(data)
Perform COPIED sorting on the
>>> data data.
[6, 3, 1, 2, 4, 5] Same as it ever was.
>>> data2
The copied data is ordered
from lowest to highest.
[1, 2, 3, 4, 5, 6]
Either sorting option works with the coach’s data, but let’s use a
copied sort for now to arrange to sort the data on output. In the
space below, provide four amended print() statements to
replace those at the bottom of your program.
Either sorting option works with the coach’s data, but let’s use
a copied sort for now to arrange to sort the data on output. You
were to provide four amended print() statements to replace
those at the bottom of your program.
print(sorted(james))
Simply call
“sorted()” on the print(sorted(julie))
data BEFORE
you display it on print(sorted(mikey))
screen.
print(sorted(sarah))
Q: What happens to the unsorted data when I use sort()? Q: And there’s no way to get the original data back?
Geek Bits
You’ve already seen method chaining, and now it’s time to say
“hello” to function chaining. Function chaining allows you to apply
a series of functions to your data. Each function takes your data,
performs some operation on it, and then passes the transformed
data on to the next function. Unlike method chains, which read
from left to right, function chains read from right to left (just to
keep things interesting).
146 Chapter 5
comprehending data
Test Drive
Let’s see if this improves your output in any way. Make the necessary amendments to your code and
run it.
updates
Here’s thede. But look at T
to the co The data is notHIS!
sorted…which is, at all
weird. like,
2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55
sarah.txt
Recall that data read from a file comes into your program as text, so Sarah’s
data looks like this once you turn it into a list of “times”:
h the coach
These are all strings, even thoug
thinks they’re times.
And when you sort Sarah’s data, it ends up in this order (which isn’t quite
what you were expecting): I don’t get what the
problem is...they’re all
times to me.
['2-25', '2-55', '2.18', '2.58', '2:39', '2:54', '2:55', '2:55', '2:58']
Python sorts the strings, and when it comes to strings, a dash comes before a
period, which itself comes before a colon. As all the strings start with 2, the
next character in each string acts like a grouping mechanism, with the dashed
times grouped and sorted, then the period times, and finally the colon times.
148 Chapter 5
comprehending data
Code Magnets
Let’s create a function called sanitize(), which takes as input
a string from each of the athlete’s lists. The function then processes
the string to replace any dashes or colons found with a period and
returns the sanitized string. Note: if the string already contains a
period, there’s no need to sanitize it.
Rearrange the code magnets at the bottom of the page to provide
the required functionality.
def sanitize(time_string):
elif ':' i
Your magnets are
n time_str
ing:
waiting. time_string.split(splitter)
return(time_stri
if '-' in ng)
time_strin
g:
else:
(mins, secs) =
'-'
splitter = ':' ter =
split
def sanitize(time_string):
if '-' in time_string:
splitter = '-'
Use the “in”
operator to elif ':' in time_string:
check if the
string contains splitter = ':'
a dash or a
colon. else: Do nothing if the string do
return(time_string)
NOT need to be sanitized. es
Of course, on its own, the sanitize() function is not enough. You need
to iterate over each of your lists of data and use your new function to convert
each of the athlete’s times into the correct format.
150 Chapter 5
comprehending data
Let’s write the code to convert your existing data into a sanitized version of itself. Create four
new lists to hold the sanitized data. Iterate over each athlete’s list data and append each
sanitized string from each list to the appropriate new list. Conclude your program by printing a
sorted copy of each new list to the screen.
What happens
to the four
“print()” print( )
statements? print( )
print( )
print( )
Let’s write the code to convert your existing data into a sanitized version of itself. You were to
create four new lists to hold the sanitized data. You were then to iterate over each athlete’s data
and append each sanitized string from each list to an appropriate new list. You were to conclude
your program by printing a sorted copy of each new list to the screen.
clean_james = []
Create four clean_julie = []
new, initially
empty lists. clean_mikey = []
clean_sarah = []
print( sorted(clean_sarah) )
152 Chapter 5
comprehending data
Test Drive
Combine your sanitize() function with your amended code from the previous page, and then
press F5 in IDLE to confirm the sorting is now working as expected.
Geek Bits
154 Chapter 5
comprehending data
Comprehending lists
Consider what you need to do when you transform one list into another. Four
things have to happen. You need to:
1. Create.
clean_mikey = [] 2. Iterate.
4. Append.
The new list is created… …by applying a …to each …within an existing list.
transformation… data item…
What’s interesting is that the transformation has been reduced to a single line
of code. Additionally, there’s no need to specify the use of the append()
method as this action is implied within the list comprehension. Neat, eh?
Let’s see some other list comprehension examples. Open up your IDLE shell and follow along with these one-liner
transformations.
Start by transforming a list of minutes into a list of seconds:
>>> mins = [1, 2, 3]
>>> secs = [m * 60 for m in mins]
>>> secs Simply multiply the
[60, 120, 180]
minute values by 60.
Given a list of strings in mixed and lowercase, it’s a breeze to transform the strings to UPPERCASE:
Let’s use your sanitize() function to transform some list data into correctly formatted times:
It’s also possible to assign the results of the list transformation back onto the original target identifier. This
example transforms a list of strings into floating point numbers, and then replaces the original list data:
>>> clean = [float(s) for s in clean]
>>> clean
The “float()” BIF converts to flo
[2.22, 2.22, 2.22] ating point.
And, of course, the transformation can be a function chain, if that’s what you need:
e data
Combining transformations on th
>>> clean
156 Chapter 5
comprehending data
Now that you know about list comprehensions, let’s write four of
them to process the coach’s four lists of timing values. Transform
each of your lists into sorted, sanitized version of themselves.
Grab your pencil and in the space provided, scribble the list
comprehensions you plan to use.
Q: So…let me get this straight: list comprehensions are good and list iterations are bad, right?
A: No, that’s not the best way to look at it. If you have to perform a transformation on every item in a list, using a list comprehension is the
way to go, especially when the transformation is easily specified on one line (or as a function chain). List iterations can do everything that list
comprehensions can, they just take more code, but iterations do provide more flexibility should you need it.
Geek Bits
Now that you know about list comprehensions, you were to write
four of them to process the coach’s four lists of timing values. You
were to transform each of your lists into sorted, sanitized version
of themselves. You were to grab your pencil and in the space
provided, scribble the list comprehensions you plan to use.
158 Chapter 5
comprehending data
Test Drive
Replace your list iteration code from earlier with your four new (beautiful) list comprehensions. Run
your program to confirm that the results have not changed.
In your haste to sanitize and sort your data, you forgot to worry about what
you were actually supposed to be doing: producing the three fastest times for each
athlete. And, of course, there’s no place for any duplicated times in your
output.
Accessing the first three data items from any list is easy. Either specify each list
item individually using the standard notation or use a list slice:
james[0]
Access each data item
you need individually. james[1]
james[2]
160 Chapter 5
comprehending data
Assume that the fourth from last line of code from your current program is changed to this:
james = sorted([sanitize(t) for t in james])
That is, instead of printing the sanitized and sorted data for James to the screen, this line of
code replaces James’s unordered and nonuniform data with the sorted, sanitized copy.
Your next task is to write some code to remove any duplicates from the james list produced
by the preceding line of code. Start by creating a new list called unique_james, and then
populate it with the unique data items found in james. Additionally, provide code to display only
the top three fastest times for James.
Hint: you might want to consider using the not in operator.
Assume that the fourth from last line of code from your current program is changed to this:
james = sorted([sanitize(t) for t in james])
That is, instead of printing the sanitized and sorted data for James to the screen, this line of
code replaces James’s unordered and non-uniform data with the sorted, sanitized copy.
Your next task was to write some code to remove any duplicates from the james list produced
by the preceding line of code. You were to start by creating a new list called unique_james
and then populate it with the unique data items found in james. Additionally, you were to
provide code to only display the top three fastest times for James.
Create the
empty list to unique_james = []
hold the unique
data items. Iterate over the
existing data…
for each_t in james: …and if the data item ISN’T
already in the new list…
if each_t not in unique_james:
m to
unique_james.append(each_t) …append the unique data ite
the new list.
Do this!
162 Chapter 5
comprehending data
Test Drive
Take all of the recent amendments and apply them to your program. Run this latest code within IDLE
when you are ready.
Remove the
duplicates.
Looking good!
It worked!
You are now displaying only the top three times for each athlete, and the
duplicates have been successfully removed.
The list iteration code is what you need in this instance. There’s a little bit of
duplication in your code, but it’s not too bad, is it?
164 Chapter 5
comprehending data
It is also possible to create and populate a set in one step. You can provide a list
of data values between curly braces or specify an existing list as an argument
to the set() BIF, which is the factory function:
Any duplicates in
the supplied list
distances = {10.6, 11, 8, 10.6, "two", 7} of data values are
ignored.
166 Chapter 5
comprehending data
List: Set:
[sings] “Anything you can do, I can do better. I can
do anything better than you.” I’m resisting the urge to say, “No, you can’t.”
Instead, let me ask you: what about handling
duplicates? When I see them, I throw them away
automatically.
Can you spell “d-a-t-a l-o-s-s”? Getting rid of data
automatically sounds kinda dangerous to me.
But that’s what I’m supposed to do. Sets aren’t
allowed duplicate values.
Seriously?
Yes. That’s why I exist…to store sets of values.
Which, when it’s needed, is a real lifesaver.
Head First
Code Review
The Head First Code Review Team has taken your code and
annotated it in the only way they know how: they’ve scribbled I think we
all over it. Some of their comments are confirmations of what can make a few
you might already know. Others are suggestions that might improvements here.
make your code better. Like all code reviews, these comments
are an attempt to improve the quality of your code.
def sanitize(time_string): A comment would
if '-' in time_string: be nice to have
splitter = '-'
here.
elif ':' in time_string:
splitter = ':'
else:
return(time_string)
(mins, secs) = time_string.split(splitter)
return(mins + '.' + secs)
an
data files, assigning the result to
data = mif.readline()
mikey = data.strip().split(',')
athlete list.
with open('sarah.txt') as saf:
going on here,
sarah = data.strip().split(',')
168 Chapter 5
comprehending data
Let’s take a few moments to implement the review team’s suggestion to turn those four with
statements into a function. Here’s the code again. In the space provided, create a function to
abstract the required functionality, and then provide one example of how you would call your
new function in your code:
Provide one
example call.
You were to take a few moments to implement the review team’s suggestion to turn those four
with statements into a function. In the space provided, your were to create a function to
abstract the required functionality, then provide one example of how you would call your new
function in your code:
170 Chapter 5
comprehending data
Test Drive
It’s time for one last run of your program to confirm that your use of sets produces the same results
as your list-iteration code. Take your code for a spin in IDLE and see what happens.
Excellent!
You’ve processed the coach’s data perfectly, while That’s great work, and just
taking advantage of the sorted() BIF, sets, what I need. Thanks! I’m
and list comprehensions. As you can imagine, you looking forward to seeing
can apply these techniques to many different you on the track soon...
situations. You’re well on your way to becoming a
Python data-munging master!
t h o n L in g o
Py ing - trans
forms
The sort() method changes the
lace” sort .
ordering of lists in-place.
• “In-p
eplaces
and then r
The sorted() BIF sorts most any data
•“List Com
go in old_l]
172 Chapter 5
6 custom data objects
The output from your last program in Chapter 5 was exactly what the coach
was looking for, but for the fact that no one can tell which athlete belongs to
which data. Coach Kelly thinks he has the solution: he’s added identification This is “sarah2.txt”, with
data to each of his data files: extra data added.
Sarah Sweeney,2002-6-17,2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55,2:22,2-21,2.22
If you use the split() BIF to extract Sarah’s data into a list, the first data Do this!
item is Sarah’s name, the second is her date of birth, and the rest is Sarah’s
timing data.
Let’s exploit this format and see how well things work. Grab the updated files from the
Head First Python website.
174 Chapter 6
custom data objects
Code Magnets
Let’s look at the code to implement the strategy outlined at the bottom of the previous page. For
now, let’s concentrate on Sarah’s data. Rearrange the code magnets at the bottom of this page to
implement the list processing required to extract and process Sarah’s three fastest times from Coach
Kelly’s raw data.
Hint: the pop() method removes and returns a data item from the specified list location.
on is
def get_coach_data(filename): The “get_coach_data()” functi
try: also from the last chapter.
with open(filename) as f:
data = f.readline()
return(data.strip().split(','))
except IOError as ioerr:
print('File error: ' + str(ioerr))
return(None)
Rearrange the
magnets here.
(sarah_name, sarah_dob)
sarah "'s fastes =
t times ar
e: " +
print(sarah_name +
get_coach_data('sarah2.txt') =
sarah.pop(0), sarah.pop
(0)
def sanitize(time_string):
if '-' in time_string:
splitter = '-'
elif ':' in time_string:
splitter = ':'
else:
return(time_string)
(mins, secs) = time_string.split(splitter)
return(mins + '.' + secs)
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
return(data.strip().split(','))
except IOError as ioerr: Use the function to turn
print('File error: ' + str(ioerr)) Sarah’s data file into a list,
return(None) and then assign it to the
“sarah” variable.
sarah = get_coach_data('sarah2.txt')
print(sarah_name +
The “pop(0)” call "'s fastest times are: " +
176 Chapter 6
custom data objects
Test Drive
Let’s run this code in IDLE and see what happens.
This output
is much more
understandable.
This program works as expected, and is fine…except that you have to name and create
Sarah’s three variables in such as way that it’s possible to identify which name, date of birth,
and timing data relate to Sarah. And if you add code to process the data for James, Julie,
and Mikey, you’ll be up to 12 variables that need juggling. This just about works for now
with four athletes. But what if there are 40, 400, or 4,000 athletes to process?
Although the data is related in “real life,” within your code things are disjointed, because
the three related pieces of data representing Sarah are stored in three separate variables.
Sarah Sweeney,2002-6-17,2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55,2:22,2-21,2.22
There’s a definite structure here: the athlete’s name, the date of birth, and
then the list of times.
Let’s continue to use a list for the timing data, because that still makes sense.
But let’s make the timing data part of another data structure, which associates
all the data for an athlete with a single variable.
We’ll use a Python dictionary, which associates data values with keys:
The “keys”
Name "Sarah Sweeney"
The associated data, also known
as the “values”
DOB "2002-6-17"
Times [2:58,2.58,2:39,2-25,2-55,2:54,2.18,2:55,2:55,2:22,2-21,2.22]
178 Chapter 6
custom data objects
Dictionary: List:
Hi there, List. I hear you’re great, but not always
the best option for complex data. That’s where I
come in.
What?!? Haven’t you heard? You can put anything
into a list, anything at all.
Isn’t it always?
Ummm, uh…I guess so.
Geek Bits
The Python dictionary is known by different names in other programming languages. If you hear other
programmers talking about a “mapping,” a “hash,” or an “associative array,” they are talking about a “dictionary.”
Let’s see the Python dictionary in action. Follow along with this IDLE session on your computer, ensuring that you
get the same results as shown.
Start by creating two empty dictionaries, one using curly braces and the other using a factory function:
>>> cleese = {}
>>> palin = dict()
Both techniques create
>>> type(cleese) an empty dictionary, as
<class 'dict'> confirmed.
>>> type(palin)
<class 'dict'>
Add some data to both of these dictionaries by associating values with keys. Note the actual structure of the data
is presenting itself here, as each dictionary has a Name and a list of Occupations. Note also that the palin
dictionary is being created at the same time:
>>> cleese['Name'] = 'John Cleese'
>>> cleese['Occupations'] = ['actor', 'comedian', 'writer', 'film producer']
>>> palin = {'Name': 'Michael Palin', 'Occupations': ['comedian', 'actor', 'writer', 'tv']}
With your data associated with keys (which are strings, in this case), it is possible to access an individual data item
using a notation similar to that used with lists:
>>> palin['Name']
Use square brackets to index into the dictionary to access
data items, but instead of numbers, index with keys.
Use numbers to access a list item stored at a particular dictionary key.
'Michael Palin'
Think of this as “index-chaining” and read from right to left: “…the last
>>> cleese['Occupations'][-1]
As with lists, a Python dictionary can grow dynamically to store additional key/value pairings. Let’s add some data
about birthplace to each dictionary:
Provide the data associated
>>> palin['Birthplace'] = "Broomhill, Sheffield, England" with the new key.
>>> cleese['Birthplace'] = "Weston-super-Mare, North Somerset, England"
Unlike lists, a Python dictionary does not maintain insertion order, which can result in some unexpected
behavior. The key point is that the dictionary maintains the associations, not the ordering:
>>> palin
{'Birthplace': 'Broomhill, Sheffield, England', 'Name': 'Michael Palin', 'Occupations':
['comedian', 'actor', 'writer', 'tv']}
>>> cleese
{'Birthplace': 'Weston-super-Mare, North Somerset, England', 'Name': 'John Cleese',
'Occupations': ['actor', 'comedian', 'writer', 'film producer']}
The ordering maintained by Python is different from how the data
was inserted. Don’t worry about it; this is OK.
180 Chapter 6
custom data objects
It’s time to apply what you now know about Python’s dictionary to your code. Let’s continue to
concentrate on Sarah’s data for now. Strike out the code that you no longer need and replace it
with new code that uses a dictionary to hold and process Sarah’s data.
def sanitize(time_string):
if '-' in time_string:
splitter = '-'
elif ':' in time_string:
splitter = ':'
else:
return(time_string)
(mins, secs) = time_string.split(splitter)
return(mins + '.' + secs)
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
return(data.strip().split(','))
except IOError as ioerr:
print('File error: ' + str(ioerr))
Strike out the code
you no longer need. return(None)
sarah = get_coach_data('sarah2.txt')
(sarah_name, sarah_dob) = sarah.pop(0), sarah.pop(0)
print(sarah_name + "'s fastest times are: " +
It’s time to apply what you now know about Python’s dictionary to your code. Let’s continue to
concentrate on Sarah’s data for now. You were to strike out the code that you no longer needed
and replace it with new code that uses a dictionary to hold and process Sarah’s data.
def sanitize(time_string):
if '-' in time_string:
splitter = '-'
elif ':' in time_string:
splitter = ':'
else:
return(time_string)
(mins, secs) = time_string.split(splitter)
return(mins + '.' + secs)
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
return(data.strip().split(','))
except IOError as ioerr:
print('File error: ' + str(ioerr)) You don’t need this
return(None)
code anymore.
sarah = get_coach_data('sarah2.txt')
(sarah_name, sarah_dob) = sarah.pop(0), sarah.pop(0)
print(sarah_name + "'s fastest times are: " +
str(sorted(set([sanitize(t) for t in sarah]))[0:3]))
Create an empty
dictionary. sarah_data = {}
sarah_data[‘Name’] = sarah.pop(0) Populate the dictionary wit
associating the data from thh ethfile data by
sarah_data[‘DOB’] = sarah.pop(0) the dictionary keys.. e with
sarah_data[‘Times’] = sarah
print(sarah_data[‘Name’] + “’s fastest times are: “ +
str(sorted(set([sanitize(t) for t in sarah_data[‘Times’]]))[0:3]))
Refer to the dictionary when
processing the data.
182 Chapter 6
custom data objects
Test Drive
Let’s confirm that this new version of your code works exactly as before by testing your code within
the IDLE environment.
Which, again, works as expected…the difference being that you can now more easily
determine and control which identification data associates with which timing data,
because they are stored in a single dictionary.
Although, to be honest, it does take more code, which is a bit of a bummer. Sometimes the
extra code is worth it, and sometimes it isn’t. In this case, it most likely is.
Head First
Code Review
It’s great to see
The Head First Code Review Team has been at it
you taking some of our
again: they’ve scribbled all over your code. Some
suggestions on board.
of their comments are confirmations; others are
Here are a few more...
suggestions. Like all code reviews, these comments
are an attempt to improve the quality of your code.
def sanitize(time_string):
if '-' in time_string:
splitter = '-'
elif ':' in time_string:
splitter = ':'
else:
return(time_string)
(mins, secs) = time_string.split(splitter)
return(mins + '.' + secs)
def get_coach_data(filename):
try:
with open(filename) as f:
ry as you go
data = f.readline()
Rather than building the dictiona
? In fact, in
along, why not do it all in one go
return(data.strip().split(','))
sense to do
this situation, it might even make
except IOError as ioerr:
oach_data()
this processing within the get_c
print('File error: ' + str(ioerr))
return a
return(None)
You might want to consider moving this code into the get_coach_data() function, too, because
doing so
would rather nicely abstract away these processing details. But whether you do or not is up to
you. It’s
your code, after all!
184 Chapter 6
custom data objects
Actually, those review comments are really useful. Let’s take the time to
apply them to your code. There are four suggestions that you need to
adjust your code to support:
1. Create the dictionary all in one go.
You were to take the time to apply the code review comments to your
code. There were four suggestions that you needed to adjust your code
to support:
1. Create the dictionary all in one go.
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
1. Create a temporary templ = data.strip().split(‘,’)
list to hold the data return({‘Name’ : templ.pop(0), 2. The dictionary creation code
BEFORE creating the now part of the function. is
dictionary all in one go. ‘DOB’ : templ.pop(0),
‘Times’: str(sorted(set([sanitize(t) for t in templ]))[0:3])})
except IOError as ioerr: e
3. The code that determinesththe
print(‘File error: ‘ + str(ioerr)) top three scores is part of
function, too.
return(None)
186 Chapter 6
custom data objects
Test Drive
Let’s confirm that all of the re-factoring suggestions from the Head First Code Review Team are
working as expected. Load your code into IDLE and take it for a spin.
tidied up and
This code has been considerably lete associated
now displays the name of the ath
with their times.
Looking
good!
To process additional athletes, all you need is two lines of code: the first invokes
the get_coach_data() function and the second invokes print().
And if you require additional functionality, it’s no big deal to write more
functions to provide the required functionality, is it?
188 Chapter 6
custom data objects
Define a class
Python follows the standard object-oriented programming model of
providing a means for you to define the code and the data it works on as a
class. Once this definition is in place, you can use it to create (or instantiate)
data objects, which inherit their characteristics from your class.
Within the object-oriented world, your code is often referred to as the class’s
methods, and your data is often referred to as its attributes. Instantiated
data objects are often referred to as instances.
190 Chapter 6
custom data objects
class Athlete:
Don’t forget the colon!
def __init__(self):
# The code to initialize a "Athlete" object.
...
def __init__(self):
# The code to initialize an "Athlete" object.
...
Check out what Python turns your object creation invocation into. Notice
anything?
192 Chapter 6
custom data objects
class Athlete:
def __init__(self, value=0):
The “init” code now
assigns a supplied value self.thing = value Note the use of
to a class attribute def how_big(self): “self” to identify
called “self.thing”. the calling object
return(len(self.thing)) instance.
When you invoke a class method on an object instance, Python arranges for
the first argument to be the invoking object instance, which is always assigned
to each method’s self argument. This fact alone explains why self is
so important and also why self needs to be the first argument to every object
method you write:
The target
The class The method indentifer (or
instance)
d.how_big() Athlete.how_big(d)
Let’s use IDLE to create some object instances from a new class that you’ll define. Start by creating a small class
called Athlete: No te the default values for
>>> class Athlete: two of the arguments.
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name
ree att rib utes are init iali ze d and assigned to three class
Th ument data.
attributes using the supplied arg
self.dob = a_dob
self.times = a_times
With the class defined, create two unique object instances which derive their characteristcs from the Athlete
class:
>>> sarah
es on our computer, which wil l
<__main__.Athlete object at 0x14d23f0> These are the memory addressed on yours. The key poi nt is
>>> james differ from the values report and “james” differ.
<__main__.Athlete object at 0x14cb7d0> the memory address for “sarah”
Now that sarah and james exist as object instances, you can use the familiar dot notation to access the
attributes associated with each:
>>> sarah.name
'Sarah Sweeney'
>>> james.name
'James Jones'
>>> sarah.dob
'2002-6-17'
The “james” object instance has
>>> james.dob
“dob”, so nothing appears on scr no value for
>>> sarah.times een.
['2:58', '2.58', '1.56']
>>> james.times
[]
194 Chapter 6
custom data objects
Here’s your code (except for the santize() function, which doesn’t
need to change). With your pencil, write code to define the Athlete
class. In addition to the __init__() method, define a new method
called top3() that, when invoked, returns the top three times.
def get_coach_data(filename):
try:
to ensure
with open(filename) as f:
What needs to change here hle object
this function returns an At te
data = f.readline()
as opposed to a dictionary?
templ = data.strip().split(',')
return({'Name' : templ.pop(0),
'DOB' : templ.pop(0),
'Times': str(sorted(set([sanitize(t) for t in templ]))[0:3])})
except IOError as ioerr:
print('File error: ' + str(ioerr))
return(None)
This line of code
james = get_coach_data('james2.txt')
needs to change, too.
Here’s your code (except for the santize() function, which doesn’t
need to change). With your pencil, you were to write code to define the
Athlete class. In addition to the __init__() method, you were to
define a new method called top3() that, when invoked, returns the top
three times. You were to be sure to adjust the get_coach_data()
function to return an Athlete object as opposed to a dictionary, and
you weren’t to forget to amend print(), too.
class Athlete:
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name There’s nothing new here as this
code is taken straight from the
self.dob = a_dob most recent IDLE session.
self.times = a_times
Did you remember to
use “self”?
def top3(self):
return(sorted(set([sanitize(t) for t in self.times]))[0:3])
196 Chapter 6
custom data objects
Test Drive
With these changes applied to your program, let’s ensure you continue to get the same results as
earlier. Load your code into IDLE and run it.
Cool! There’s no
change here.
Q: I’m not sure I see why the top3() method is coded to return Q: OK, I think I’m convinced. But tell me: how do I go about
a three-item list, as opposed to a string? Surely a string would adding more times to my existing Athlete objects?
make the print() statement in the main program easier to write?
Q: Why does the class even need the top3() method? Why
Additionally, you can add a list of times by defining a method called
add_times().Then all you need to do in your code is say
not store the top three times as an attribute within the class and something like this:
create it as part of the object’s creation? sarah.add_time('1.31')
A:
to add a single time to Sarah’s timing data, or say this:
Again, better not to, because doing so is less flexible. If you james.add_times(['1.21','2.22'])
compute and store the top three times at object creation, you make it to add a bunch of times to James’s data.
harder to extend the list of timing data associated with the object.
For instance, if you add more timing data after the object is created,
Q: But surely, knowing that times is a list, I could write code
like this to do the same thing?
you’ll need to arrange to recompute the top three (because the new sarah.times.append('1.31')
times might be fast) and update the attribute. However, when you james.times.append(['1.21','2.22'])
compute the top three times “on the fly” using a call to the top3()
method, you always ensure you’re using the most up-to-date data.
A: You could, but that would be a disaster.
Q: OK, I get that. But, with a little extra work, I could do it Q: What?!? Why do you say that? There’s nothing wrong
during object creation, right?
with my suggestion, is there?
198 Chapter 6
custom data objects
Let’s add two methods to your class. The first, called add_time(), appends a single
additional timing value to an athlete’s timing data. The second, add_times(), extends an
athlete’s timing data with one or more timing values supplied as a list.
Here’s your current class: add the code to implement these two new methods.
class Athlete:
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name
self.dob = a_dob
self.times = a_times
def top3(self):
return(sorted(set([sanitize(t) for t in self.times]))[0:3])
Don’t put down the pencil just yet! Provide a few lines of code to
test your new functionality:
Let’s add two methods to your class. The first, called add_time(), appends a single
additional timing value to an athlete’s timing data. The second, add_times(), extends an
athlete’s timing data with one of more timing values supplied as a list.
Here’s your current class: you were to add the code to implement these two new methods.
class Athlete:
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name
self.dob = a_dob
self.times = a_times
def top3(self):
return(sorted(set([sanitize(t) for t in self.times]))[0:3])
print(vera.top3()) The top 3 timing scores are now: 1.21, 1.31 and 2.22.
200 Chapter 6
custom data objects
Do this!
Test Drive
After running your existing program, try out your test code in the IDLE shell to confirm that
everything is working as expected.
As expected.
Great: it worked.
You’ve packaged your code with your data and created a custom class
from which you can create objects that share behaviors. And when extra
functionality is required, add more methods to implement the required
functionality.
By encapsulating your athlete code and data within a custom class, you’ve
created a much more maintainable piece of software. You will thank
yourself for doing this when, in six months, you need to amend your code.
202 Chapter 6
custom data objects
Slippery
lawyer-type
204 Chapter 6
custom data objects
Let’s see what’s involved in inheriting from Python’s built-in list class. Working in IDLE’s shell, start by creating a
custom list derived from the built-in list class that also has an attribute called name:
>>> class NamedList(list): Provide the name of the class that this new
def __init__(self, a_name): class derives from.
list.__init__([])
self.name = a_name Initialize the derived from class, and then
assign the argument to the attribute.
With your NamedList class defined, use it to create an object instance, check the object’s type (using the
type() BIF), and see what it provides (using the dir() BIF):
>>> johnny = NamedList("John Paul Jones") Create a new “NamedList” object instance.
>>> type(johnny)
<class '__main__.NamedList'>
Yes, “johnny” is a “NamedList”.
>>> dir(johnny)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__',
'__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__module__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'append', 'count', 'extend', 'index', 'insert', 'name', 'pop', 'remove',
'reverse', 'sort']
“johnny” can do everything a list can, as well as
store data in the “name” attribute.
Use some of the functionality supplied by the list class to add to the data stored in johnny:
Here is the code for the now defunct Athlete class. In the space provided below, rewrite this
class to inherit from the built-in list class. Call your new class AthleteList. Provide a
few lines of code to exercise your new class, too:
class Athlete:
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name
self.dob = a_dob
self.times = a_times
def top3(self):
return(sorted(set([sanitize(t) for t in self.times]))[0:3])
Exercise your
code here.
Here is the code for the now defunct Athlete class. In the space provided below, you were
to rewrite this class to inherit from the built-in list class. You were to call your new class
AthleteList, as well as provide a few lines of code to exercise your new class:
class Athlete:
def __init__(self, a_name, a_dob=None, a_times=[]):
self.name = a_name
self.dob = a_dob
self.times = a_times
def top3(self):
return(sorted(set([sanitize(t) for t in self.times]))[0:3])
208 Chapter 6
custom data objects
Do this!
Q: Sorry…but not three minutes ago you were telling me not Q: Can I inherit from my own custom classes?
A:
to expose the inner workings of my class to its users, because
that was fundamentally a bad idea. Now you’re doing the exact Of course, that’s the whole idea. You create a generic class
opposite! What gives? that can then be “subclassed” to provide more specific, targeted
A: functionality.
Q:
Well spotted. In this particular case, it’s OK to expose the fact
that the class is built on top of list. This is due to the fact that the Can I put my class in a module file?
class is deliberately called AthleteList to distinguish it from
the more generic Athlete class. When programmers see the
word “list” in a class name, they are likely to expect the class to work
A: Yes, that’s a really good idea, because it lets you share your
class with many of your own programs and with other programmers.
like a list and then some. This is the case with AthleteList. For instance, if you save your AthleteList class to a file
Q: What about inheriting from more than one class…does then use the class as if it was defined in your current program. And,
of course, if you create a really useful class, pop it into its own
Python support multiple interitance?
module and upload it to PyPI for the whole world to share.
A: Yes, but it’s kind of scary. Refer to a good Python reference text
for all the gory details.
Test Drive
One last run of your program should confirm that it’s working to specification now. Give it a go in
IDLE to confirm.
Your entire
program now
produces the
output the
coach wants.
210 Chapter 6
custom data objects
Good job!
CHAPTER 6
belt and you’ve added some key
CHAPTER 6
g o
person, use the familiar square bracket
Py th on L i n notation: person['Name'].
ata
a built-in d
ionary” - ows you to
Like list and set, a Python’s dictionary
• “Dict
dynamically grows as new data is added
hat all
structure tata values with keys.
to the data structure.
associate d of
k-up part
Populate a dictionary as you go:
- t h e lo o new_d = {} or new_d = dict()
• “Key”
ary. and then
the diction a p art of the , d['Name'] = 'Eric Idle'
h e d at e
• “Value
”-t c a n be any valu .
or do the same thing all in the one go:
( w h ic h )
dictionary other data structure
new_d = {'Name': 'Eric
including an
Idle'}
The class keyword lets you define a
class.
212 Chapter 6
7 web development
Sooner or later, you’ll want to share your app with lots of people.
You have many options for doing this. Pop your code on PyPI, send out lots of emails, put
your code on a CD or USB, or simply install your app manually on the computers of those
people who need it. Sounds like a lot of work…not to mention boring. Also, what happens
when you produce the next best version of your code? What happens then? How do you
manage the update? Let’s face it: it’s such a pain that you’ll think up really creative excuses
not to. Luckily, you don’t have to do any of this: just create a webapp instead. And, as this
chapter demonstrates, using Python for web development is a breeze.
Coach Kelly’s
young athletes
214 Chapter 7
web development
Webapps Up Close
No matter what you do on the Web, it’s all about requests and responses. A web request is sent
from a web browser to a web server as the result of some user interaction. On the web server, a
web response (or reply) is formulated and sent back to the web browser. The entire process can
be summarized in five steps.
The Internet
a web
Here comes
request.
216 Chapter 7
web development
Web
Server
The Internet
218 Chapter 7
web development
There’s nothing like grabbing your pencil and a few blank paper
napkins to quickly sketch a simple web design. You probably
need three web pages: a “welcome” page, a “select an athlete”
page, and a “display times” page. Go ahead and draw out a rough
design on the napkins on this page, and don’t forget to draw any
linkages between the pages (where it makes sense).
§ There’s nothing like grabbing your pencil and a few blank paper
napkins to quickly sketch a simple web design. You probably
need three web pages: a “welcome” page, a “select an athlete”
The home page page, and a “display times” page. You were to draw out a rough
displays a friendly design on the napkins. You were to draw any linkages between
graphic and a link to the pages (where it made sense).
start the web app.
go to
Click on the home page’s linkoftoall the
a page that displays a list athlete’s
coach’s athletes. Click on an“Select”
radio button and then the
button to see the data.
Select an athlet
e from this list to
work with:
e to Co a ch K el ly’s Website Sarah
Welcom
w , al l t h at yo u’ ll find here is my James
For n o
data.
athlete’s timing Julie
rack!
See you on the t Mikey
Select
220 Chapter 7
web development
The Model
The code to store (and sometimes process) your webapp’s data
The View
The code to format and display your webapp’s user interface(s)
The Controller
The code to glue your webapp together and provide its business logic
By following the MVC pattern, you build your webapp in such as way as to
enable your webapp to grow as new requirements dictate. You also open up the
possibility of splitting the workload among a number of people, one for each
component.
mikey.txt
didn’t!”, ‘You did not!’,
stored in a
‘Ah! (taking out his wallet
and paying) Just the five
minutes.’, ‘You most
certainly did not!’, “Oh
no you didn’t!”, “Oh no
you didn’t!”, “Oh look,
this isn’t an argument!”,
dictionary
“No it isn’t!”, “It’s
just contradiction!”,
‘It IS!’, ‘You just
contradicted me!’, ‘You
DID!’, ‘You did just
then!’, ‘(exasperated)
Oh, this is futile!!’,
‘Yes it is!’]
sarah.txt
The
get_from_store()
The function
put_to_store()
function
'James': AthleteList... ,
didn’t!”, ‘You did not!’,
‘Ah! (taking out his wallet
and paying) Just the five
minutes.’, ‘You most
certainly did not!’, “Oh
no you didn’t!”, “Oh no
you didn’t!”, “Oh look,
this isn’t an argument!”,
“No it isn’t!”, “It’s
'Julie': AthleteList... ,
just contradiction!”,
‘It IS!’, ‘You just
contradicted me!’, ‘You
DID!’, ‘You did just
then!’, ‘(exasperated)
Oh, this is futile!!’,
‘Yes it is!’]
'Mikey': AthleteList... }
The single pickle with
all of the data stored s returned
in a dictionary A dictionary of AthleteList)” function
from the “get_from_sto re(
222 Chapter 7
web development
Here is the outline for a new module called athletemodel.py, which provides the
functionality described on the previous page. Some of the code is already provided for you. Your
job is to provide the rest of the code to the put_to_store() and get_from_store()
functions. Don’t forget to protect any file I/O calls.
import pickle
from athletelist import AthleteList
def get_coach_data(filename):
# Not shown here as it has not changed since the last chapter.
return(all_athletes)
def get_from_store():
all_athletes = {} Both functions
need to return
a dictionary of
Get the dictionary AthleteLists.
from the file, so
that it can be
returned to the
caller.
return(all_athletes)
Here is the outline for a new module called athletemodel.py, which provides the
functionality described on the previous page. Some of the code is already provided for you. Your
job was to provide the rest of the code to the put_to_store() and get_from_store()
functions. You were not to forget to protect any file I/O calls.
import pickle
from athletelist import AthleteList
def get_coach_data(filename):
# Not shown here as it has not changed since the last chapter.
def put_to_store(files_list):
all_athletes = {}
def get_from_store():
all_athletes = {}
try:
Simply read the with open(‘athletes.pickle', ‘rb') as athf:
entire pickle into
the dictionary. all_athletes = pickle.load(athf)
What could be Again…don’t
easier? except IOError as ioerr: forget your
try/except.
print(‘File error (get_from_store): ' + str(ioerr))
return(all_athletes)
224 Chapter 7
web development
Let’s test your code to ensure that it is working to specification. Type your code into an IDLE edit window and save
your code into a folder that also includes the coach’s text files. Press F5 to import your code to the IDLE shell, and
then use the dir() command to confirm that the import has been successful:
>>> dir()
['AthleteList', '__builtins__', '__doc__', '__name__', '__package__', 'get_coach_data’,
'get_from_store', 'pickle', 'put_to_store']
Create a list of files to work with, and then call the put_to_store() function to take the data in the list of files
and turn them into a dictionary stored in a pickle:
Use the existing data in the data dictionary to display each athlete’s name and date of birth:
>>> for each_athlete in data:
print(data[each_athlete].name + ' ' + data[each_athlete].dob)
b”
By accessing the “name” andth“do
James Lee 2002-3-14
e rest of
attributes, you can get at
Sarah Sweeney 2002-6-17
Use the get_from_store() function to load the pickled data into another dictionary, then confirm that the
results are as expected by repeating the code to display each athlete’s name and date of birth:
226 Chapter 7
web development
def start_response(resp="text/html"):
return('Content-type: ' + resp + '\n\n')
def include_header(the_title):
documentation, or anything! ,
header = Template(head_text)
return(header.substitute(title=the_title))
def include_footer(the_links):
with open('templates/footer.html') as footf:
foot_text = footf.read()
link_string = ''
for key in the_links:
link_string += '<a href="' + the_links[key] + '">' + key + '</a> '
footer = Template(foot_text)
return(footer.substitute(links=link_string))
def end_form(submit_msg="Submit"):
return('<p></p><input type=submit value="' + submit_msg + '">')
def u_list(items):
u_string = '<ul>'
for item in items:
u_string += '<li>' + item + '</li>'
u_string += '</ul>'
return(u_string)
def para(para_text):
return('<p>' + para_text + '</p>')
Let’s get to know the yate code before proceeding with the rest of this chapter. For each chunk
of code presented, provide a written description of what you think it does in the spaces provided:
def start_response(resp="text/html"):
return('Content-type: ' + resp + '\n\n')
One has already This function takes a single (optional) string as its argument and uses it to
been done for you.
create a CGI “Content-type:” line, with “text/html” as the default.
def include_header(the_title):
with open('templates/header.html') as headf:
head_text = headf.read()
header = Template(head_text)
return(header.substitute(title=the_title))
def include_footer(the_links):
with open('templates/footer.html') as footf:
foot_text = footf.read()
link_string = ''
for key in the_links:
link_string += '<a href="' + the_links[key] + '">' + key +
'</a> '
footer = Template(foot_text)
return(footer.substitute(links=link_string))
228 Chapter 7
web development
def end_form(submit_msg="Submit"):
return('<p></p><input type=submit value="' + submit_msg + '"></form>')
def u_list(items):
u_string = '<ul>'
for item in items:
u_string += '<li>' + item + '</li>'
u_string += '</ul>'
return(u_string)
def para(para_text):
return('<p>' + para_text + '</p>')
Let’s get to know the yate code before proceeding with the rest of this chapter. For each chunk
of code presented, you were to provide a written description of what you think it does:
This function takes a single (optional) string as its argument and uses it to
create a CGI “Content-type:” line, with “text/html” as the default.
Open the def include_header(the_title):
This function takes a single string as its argument and uses at the title for
the start of a HTML page. The page itself is stored within a separate file
in “templates/header.html”, and the title is substituted in as needed.
Open the template
file (which is
HTML), read it in, def include_footer(the_links):
230 Chapter 7
web development
This function returns the HTML for the start of a form and lets the caller
specify the URL to send the form’s data to, as well as the method to use.
def end_form(submit_msg="Submit"):
return('<p></p><input type=submit value="' + submit_msg + '"></form>')
This function returns the HTML markup, which terminates the form while
allowing the caller to customize the text of the form’s “submit” button.
def radio_button(rb_name, rb_value):
return('<input type="radio" name="' + rb_name +
'" value="' + rb_value + '"> ' + rb_value + '<br />')
Given a radio-button name and value, create a HTML radio button (which is
typically included within a HTML form). Note: both arguments are required.
def u_list(items):
u_string = '<ul>'
Given a list of items, this function turns the list into a HTML unnumbered
list. A simple “for” loop does all the work, adding a LI to the UL element
with each iteration.
def header(header_text, header_level=2):
return('<h' + str(header_level) + '>' + header_text +
'</h' + str(header_level) + '>')
Create and return a HTML header tag (H1, H2, H2, and so on) with level 2
as the default.. The “header_text” argument is required.
def para(para_text):
return('<p>' + para_text + '</p>')
Q: Where are the HTML templates used in the include_ Q: And you did this because you are using MVC?
A:
header() and include_footer() functions?
Q: Why do I need yate at all? Why not include the HTML that I Q: But surely MVC is overkill for something this small?
A:
need right in the code and generate it with print() as needed?
A: We don’t think so, because you can bet that your webapp will
You could, but it’s not as flexible as the approach shown grow, and when you need to add more features, the MVC “separation
here. And (speaking from bitter experience) using a collection of of duties” really shines.
print() statements to generate HTML works, but it turns your
code into an unholy mess.
Let’s get to know the yate module even more. With the code downloaded and tucked away in an easy-to-
find folder, load the module into IDLE and press F5 to take it for a spin. Let’s start by testing the start_
response() function. The CGI standard states that every web response must start with a header line that
indictes the type of the data included in the request, which start_response() lets you control:
>>> start_response() The default CGI response heade
'Content-type: text/html\n\n' plus variations on a theme. r,
>>> start_response("text/plain")
'Content-type: text/plain\n\n'
>>> start_response("application/json")
'Content-type: application/json\n\n'
The include_header() function generates the start of a web page and let’s you customizee its title:
>>> include_header("Welcome to my home on the web!")
'<html>\n<head>\n<title>Welcome to my home on the web!</title>\n<link type="text/css"
rel="stylesheet" href="/coach.css" />\n</head>\n<body>\n<h1>Welcome to my home on the web!</
h1>\n'
232 Chapter 7
web development
The include_footer() function produces HTML that terminates a web page, providing links (if provided as a
dictionary). An empty dictionary switches off the inclusion of the linking HTML:
>>> start_form("/cgi-bin/process-athlete.py")
The argument allows you to specif
'<form action="/cgi-bin/process-athlete.py" method="POST">' the name of the program on the y
>>> end_form() server to send the form’s data
'<p></p><input type=submit value="Submit"></form>'
to.
>>> end_form("Click to Confirm Your Order")
'<p></p><input type=submit value="Click to Confirm Your Order"></form>'
HTML radio buttons are easy to create with the radio_button() function:
>>> for fab in ['John', 'Paul', 'George', 'Ringo']:
Unordered list are a breeze with the u_list() function: Again, not too easy on your eye,
u_list(['Life of Brian', 'Holy Grail']) fine as far as your web browse but
'<ul><li>Life of Brian</li><li>Holy Grail</li></ul>' concerned. r is
The header() function lets you quickly format HTML headings at a selected level (with 2 as the default):
>>> header("Welcome to my home on the web")
'<h2>Welcome to my home on the web</h2>'
Nothing too exciting here, but it works
as expected. Same goes for here.
>>> header("This is a sub-sub-sub-sub heading", 5)
'<h5>This is a sub-sub-sub-sub heading</h5>'
Last, but not least, the para() function encloses a chunk of text within HTML paragraph tags:
>>> para("Was it worth the wait? We hope it was...")
'<p>Was it worth the wait? We hope it was...</p>'
234 Chapter 7
web development
CGI
Web
So...to run my webapp,
More on this in
Server
I need a web server with
a little bit. CGI enabled.
your server.
It wouldn’t hurt to
add a title to this
web page, would it?
This is a paragraph.
Select
A “submit” button
When your user selects an athlete by clicking on her radio button and clicking
Select, a new web request is sent to the web server. This new web request
contains data about which radio button was pressed, as well as the name of a CGI
script to send the form’s data to.
Recall that all of your CGI scripts need to reside in the cgi-bin folder on
your web server. With this in mind, let’s make sure your generate_list.py
CGI script sends its data to another program called:
cgi-bin/generate_timing_data.py
236 Chapter 7
web development
Pool Puzzle
Your job is to take the code from the pool and
place them into the blank lines in the CGI
script. You may not use the same line
of code more than once. Your goal is to
make a CGI script that will generate a
HTML page that matches the hand-drawn
design from the previous page.
import athletemodel
import yate
import glob
Start generating the
web page, providing an
Always start with a
data_files = glob.glob("data/*.txt")
appropriate title.
Content-type line.
athletes = athletemodel.put_to_store(data_files)
Generate a radio-
print(yate.radio_button("which_athlete", athletes[each_athlete].name))
End the form generation with a
button for each of custom
print(yate.end_form("Select"))
“Submit” button.
your athletes. print(yate.include_footer({"Home": "/index.html"}))
Cool…an
empty pool.
238 Chapter 7
web development
Test Drive
To test drive your CGI script, you need to have a web server up and running. The code to
simplehttpd.py is included as part of the webapp.zip download. After you unpack the ZIP
file, open a terminal window in the webapp folder and start your web server:
The “timing
Starting simple_httpd on port: 8080
data” hyperlink is
localhost - - [12/Sep/2010 14:30:03] "GET / HTTP/1.1" 200 -
localhost - - [12/Sep/2010 14:30:03] "GET /coach.css HTTP/1.1" 200 -
waiting for you localhost - - [12/Sep/2010 14:30:03] "GET /images/coach-head.jpg HTTP/1.1" 200 -
to click it. localhost - - [12/Sep/2010 14:30:03] "GET /favicon.ico HTTP/1.1" 200 -
240 Chapter 7
web development
Sure enough, clicking on the home page’s link runs the generate_list.py program on the web
server, which displays Coach Kelly’s athletes as a list of radio buttons.
You can click the Home hyperlink to return to the coach’s home page, or
select an athlete from the list (by clicking on their radio-button), before
pressing the Select button to continue.
Check the web server’s console window to confirm that your attempt to post your
form’s data to generate_timing_data.py resulted in failure.
Which isn’t really that surprising seeing as you have yet to write that code! So…things
aren’t as bad as they first appear. The “404” error is exactly what you would expect
to be displayed in this situation, so your generate_list.py CGI is working
fine. What’s needed is the code to the other CGI script.
242 Chapter 7
web development
2.21
This might be best 2.22
rendered as an
unordered HTML list.
Home Select another athlete.
script here.
import cgi
Import the
libraries and import athletemodel
modules you
intend to use. import yate
Nothing print(yate.start_response())
new here
or here. print(yate.include_header("Coach Kelly's Timing Data"))
print(yate.header("Athlete: " + athlete_name + ", DOB: " + Grab the
athlete’s name
athletes[athlete_name].dob + ".")) and DOB.
print(yate.para("The top times for this athlete are:"))
Turn the top three list into
print(yate.u_list(athletes[athlete_name].top3())) an unordered HTML list.
The bottom of print(yate.include_footer({"Home”: "/index.html",
this web page
has two links. "Select another athlete": "generate_list.py"}))
A link back to the previous
CGI script.
246 Chapter 7
web development
Your web server should still be running from earlier. If it isn’t, start it again. In your web browser,
return to the coach’s home page, then select the hyperlink to display the list of athletes, select Sarah,
and then press the button.
This all
looks OK.
Ah, phooey!
Something’s not
quite right here.
Where’s Sarah’s
top three times?
File Edit Window Help HoustonWeHaveAProblem
$ python3 simple_httpd.py
Starting simple_httpd on port: 8080
localhost - - [12/Sep/2010 14:30:03] "GET / HTTP/1.1" 200 -
Does the localhost - - [12/Sep/2010 14:30:03] "GET /coach.css HTTP/1.1" 200 -
logging
localhost - - [12/Sep/2010 14:30:03] "GET /favicon.ico HTTP/1.1" 200 -
information
localhost - - [12/Sep/2010 14:45:16] "GET /cgi-bin/generate_list.py HTTP/1.1" 200 -
localhost - - [12/Sep/2010 16:12:27] “GET /cgi-bin/generate_list.py HTTP/1.1” 200 -
tell you localhost - - [12/Sep/2010 16:12:29] “POST /cgi-bin/generate_timing_data.py HTTP/1.1” 200 -
anything? Traceback (most recent call last):
File “/Users/barryp/HeadFirstPython/chapter7/cgi-bin/generate_timing_data.py”, line 21, in <module>
print(yate.u_list(athletes[athlete_name].top3()))
TypeError: ‘list’ object is not callable
localhost - - [12/Sep/2010 16:12:29] CGI script exit status 0x100
Your CGI has suffered from a TypeError exception, but other than looking at the web
server’s logging screen, it’s not clear on the web browser screen that anything has gone wrong.
Web
Server
This behavior is fine when the webapp is deployed, but not when it’s being
developed. Wouldn’t it be useful to see the details of the exception in the
browser window, as opposed to constantly having to jump to the web server’s
logging screen?
Well…guess what? Python’s standard library comes with a CGI tracking
module (called cgitb) that, when enabled, arranges for detailed error
messages to appear in your web browser. These messages can help you work
out where your CGI has gone wrong. When you’ve fixed the error and your
CGI is working well, simply switch off CGI tracking:
Add these two lines near the
start of your CGI scripts to
import cgitb enable Python’s CGI tracking
cgitb.enable() technology.
248 Chapter 7
web development
Test Drive
Add the two CGI tracking lines of code near the top of your generate_timing_data.py CGI
script. Press the Back button on your web browser and press the Select button again. Let’s see what
Wow! Look at all happens this time.
of this detail.
The use of the @property decorator allows the top3() method to appear
like an attribute to users of the class. So, instead of calling the top3() A method call
method like this: always needs the
parentheses…
print(yate.u_list(athletes[athlete_name].top3()))
Treat the top3() method as if it was another class attribute, and call it like
this:
print(yate.u_list(athletes[athlete_name].top3))
It’s a small change, but it’s an important one …unless the method is declared
to be an “@property”, in which
When a change is made to the way a class is used, you need to be careful to case parentheses are NOT
consider what impact the change has on existing programs, both yours and required.
those written by others.
At the moment, you are the only one using the AthleteList class, so it’s
not a big deal to fix this. But imagine if thousands of programmers were
using and relying on your code…
250 Chapter 7
web development
Test Drive
Make the small edit to your code to remove the brackets from the call to the top3() method, press
your web browser’s Back button, and press the Select button one last time.
Q: What happens if the coach recruits new athletes? Q: Shouldn’t the server’s data be in a database as opposed
A:
to a pickle? Surely that would be better, right?
All Coach Kelly needs do is create a new text file similar to the
others, and your webapp handles the rest by dynamically including
the new athlete the next time your webapp runs, which occurs when
A: In this case, it’s probably overkill to use a database, but it might
be worth considering sometime in the future.
someone clicks on the home page’s “timing data” hyperlink.
By moving your program to the Web, you’ve made it a no-brainer for Coach
Kelly to share his data with not only his athletes, but with anyone else that
needs to access his data.
By conforming to the MVC pattern and using CGI, you’ve built a webapp in
such a way that it’s easy to extend as new requirements are identified.
252 Chapter 7
web development
CHAPTER 7
belt and you’ve added some key
Python techiques to your toolbox.
The Model-View-Controller pattern lets
you design and build a webapp in a
maintainable way.
The model stores your webapp’s data.
Python Lingo
The view displays your webapp’s user
interface.
Web Lingo
module can be used to build a simple web
server in Python.
” - a p r ogr a m that runs on
• “webapp
The standard library cgi module
the Web.
provides support for writing CGI scripts.
sp on se ” - se n t from the web Set the executable bit with the chmod
• “web re
to t h e w eb b r owser in repsonse +x command on Linux and Mac OS X.
server .
to a web request
The standard library cgitb module,
eway when enabled, lets you see CGI coding
• “CGI” -
the Common Gateb server to errors within your browser.
allows a w
Interface, which e program. Use cgitb.enable() to switch on
run a server-sid a
CGI tracking in your CGI code.
Small devices
256 Chapter 8
mobile app development
Obviously, the coach needs to access his data and run his webapp
on his phone…but what’s the best way to do this if not through the
phone’s browser?
258 Chapter 8
mobile app development
Web
The Internet
Server
Web
The Internet
Server
The Android
SDK website
Your AVD is
a simulated
Click on
“Create AVD”. Android phone.
you are here 4 261
emulate sl4a
On the emulator,
tap on the
“boxed” bar code
to start the
SL4A download.
When the download completes, select the emulator’s Menu button ➝ More ➝
Downloads, and then tap on the sl4a_r2.apk file to install the SL4A The version available to you : mig ht be
rry do wn load the
package on the emulator. When the install completes, tap Done. different, but don’t wo
latest release.
ferent than
The version you see might bethdif st recent.
this. Don’t worry; yours in e mo
262 Chapter 8
mobile app development
The Python for Android app runs. When you are ready, tap Open -> Install This last bit is really
to complete the installation. This downloads, extracts, and installs the Python important.
support files for Android, which can take a few minutes to complete. When
it does, Python 2.6.2 and Python for Android are installed on your emulator
and ready for action.
The “menu”
button.
Be sure to set
the SL4A
rotation mode
to automatic.
Your screen might
switch to landscape by default
the first time you run a script.
To fix this, choose Menu ➝
Preferences, scroll down to
Take your Android emulator for a spin Rotation mode, and set its
Here’s a four-line Python script that you can create to test your installation. value to Automatic.
Let’s call this script mydroidtest.py:
Your script should now appear on the list of scripts available to SL4A.
264 Chapter 8
mobile app development
Test Drive
Let’s confirm that your Android setup is working. With the SL4A app open, simply tap on your script’s
name to run it, and then click the run wheel from the menu.
Your Android
emulator with SL4A
is working, and it’s
running your Python
And there’s your message. It code.
works!
you are here 4 265
what to do?
Nothing’s really
changed...you just
have to get the web
data onto the phone.
Frank: Well…first off, the view code no longer has to generate HTML,
so that makes things interesting.
Jill: In fact, you need the web server only to supply your data on
request, not all that generated HTML.
Joe: Ah ha! I’ve solved it. Just send the pickle with all the data from the
server to the Android phone. It can’t be all that hard, can it?
Jill: Sorry, guys, that’ll cause problems. The pickle format used by
Python 3 is incompatible with Python 2. You’ll certainly be able to send
the pickle to the phone, but the phone’s Python won’t be able to work
with the data in the pickle.
Frank: Darn…what are our options, then? Plain data?
Frank Joe
Jill Joe: Hey, good idea: just send the data as one big string and parse it on
the phone. Sounds like a workable solution, right?
Jill: No, that’s a potential disaster, because you never know in what
format that stringed data will arrive. You need an data interchange format,
something like XML or JSON.
Frank: Hmm…I’ve heard XML is a hound to work with…and it’s
probably overkill for this simple app. What’s the deal with JSON?
Joe: Yes, of course, I keep hearing about JSON. I think they use it in
lots of different places on the Web, especially with AJAX.
Frank: Oh, dear…pickle, XML, JSON, and now AJAX…I think my
brain might just explode here.
Jill: Never worry, you only need to know JSON. In fact, you don’t even
need to worry about understanding JSON at all; you just need to know
how to use it. And, guess what? JSON comes standard with Python
2 and with Python 3…and the format is compatible. So, we can use
JSON on the web server and on the phone.
Frank & Joe: Bonus! That’s the type of technology we like!
266 Chapter 8
mobile app development
JSON Exposed
This week’s interview:
The Data Interchange Lowdown
Head First: Hello, JSON. Thanks for agreeing to Python, use JSON to convert it to JSON’s object
talk to us today. notation, and then send the converted data to
another computer running a program written in C#?
JSON: No problem. Always willing to play my part
in whatever way I can. JSON: And as long as C# has a JSON library, you
can recreate the Python data as C# data. Neat, eh?
Head First: And what is that, exactly?
Head First: Yes, that sounds interesting…only
JSON: Oh, I’m just one of the most widely used
[winks] why would anyone in their right mind want
data interchange formats on the Web. When you
to program in C#?
need to transfer data over the Internet, you can rely
on me. And, of course, you’ll find me everywhere. JSON: [laughs] Oh, come on now: be nice. There’s
plenty of reasons to use different programming
Head First: Why’s that?
languages for different reasons.
JSON: Well…it’s really to do with my name. The
Head First: Which goes some of the way to explain
“JS” in JSON stands for “JavaScript” and the “ON”
why we have so many great programming titles, like
stands for “Object Notation.” See?
Head First C#, Head First Java, Head First PHP and
Head First: Uh…I’m not quite with you. MySQL, Head First Rails, and Head First JavaScript.
JSON: I’m JavaScript’s object notation, which JSON: Was that a shameless, self-serving plug?
means I’m everywhere.
Head First: You know something…I think it might
Head First: Sorry, but you’ve completely lost me. well have been! [laughs].
JSON: The first two letters are the key ones: I’m JSON: [laughs] Yes, it pays to advertise.
a JavaScript standard, which means you’ll find me
Head First: And to share data, right?
everywhere JavaScript is…which means I’m in every
major web browser on the planet. JSON: Yes! And that’s exactly my point: when you
need a language-neutral data interchange format that is
Head First: What’s that got to do with Python?
easy to work with, it’s hard to pass me by.
JSON: That’s where the other two letters come
Head First: But how can you be “language neutral”
into play. Because I was initially designed to allow
when you have JavaScript in your name?
JavaScript data objects to be transferred from one
JavaScript program to another, I’ve been extended JSON: Oh, that’s just my name. It’s what they
to allow objects to be transferred regardless of what called me when the only language I supported was
programming language is used to create the data. JavaScript, and it kinda stuck.
By using the JSON library provided by your favorite
Head First: So they should really call you
programming language, you can create data that
something else, then?
is interchangeable. If you can read a JSON data
stream, you can recreate data as you see fit. JSON: Yes, but “WorksWithEveryProgramming
LanguageUnderTheSunIncludingPythonObject
Head First: So I could take an object in, say,
Notation” doesn’t have quite the same ring to it!
268 Chapter 8
mobile app development
JSON is an established web standard that comes preinstalled with Python 2 and Python 3. The JSON API is not that
much different to the one used by pickle:
Import the JSON library.
>>> import json
>>> names = ['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
>>> names Create a list of lists.
['John', ['Johnny', 'Jack'], 'Michael', ['Mike', 'Mikey', 'Mick']]
Web
Server
270 Chapter 8
mobile app development
With your new function written and added to the athletemodel module, create a new CGI
script that, when called, returns the data from the get_names_from_store() function to
the web requester as a JSON data stream.
Call your new script cgi-bin/generate_names.py.
Hint: Use application/json as your Content-type.
With your new function written and added to the athletemodel module, you were to create
a new CGI script that, when called, returns the data from the get_names_from_store()
function to the web requester as a JSON data stream.
You were to call your new script cgi-bin/generate_names.py.
import yate
272 Chapter 8
mobile app development
Test Drive
If it is not already running, start your web server and be sure to set the executable bit with the
chmod +x cgi-bin/generate_names.py command (if on Linux or Mac OS X). When you’re
ready, grab your favorite web browser and take your new CGI for a spin.
I in your
Enter the web address of the CG
browser’s location bar.
The web server’s localhost - - [18/Sep/2010 06:31:29] "GET /cgi-bin/generate_names.py HTTP/1.1" 200 -
logging information
localhost - - [18/Sep/2010 06:35:29] "GET /cgi-bin/generate_list.py HTTP/1.1" 200 -
That worked!
Now all you have to do is arrange for the Android emulator to request the
data within a Python script and display the list of names on the smartphone’s
screen. How hard can that be?
Recall the code from earlier, which demonstrated a minimal Android SL4A
app:
Import the “android”
library and create a new import android
app object instance.
app = android.Android()
msg = "Hello from Head First Python on Android"
Create an appropriate
message and display it on app.makeToast(msg)
screen.
Six calls to the Android API let you create a list of selectable items in a dialog,
together with positive and negative buttons, which are used to indicate the
selection your user made. Note how each of the calls to the Android “dialog”
API results in something appearing on screen.
app.dialogCreateAlert("Select an athlete:")
app.dialogSetSingleChoiceItems(['Mikey', 'Sarah', 'James', 'Julie'])
app.dialogSetPositiveButtonText("Select")
app.dialogSetNegativeButtonText("Quit")
app.dialogShow()
resp = app.dialogGetResponse().result
274 Chapter 8
mobile app development
get_names_cgi)))
athlete_names = sorted(json.loads(send_to_server(web_server +
status_update(qu resp = app
it_msg) .dialogGet
Response()
.result
app.dialogShow() app.dialogCreateAlert(list_title) status_update(he
llo_msg)
):
e(msg, how_long=2
def status_updat app.dialogSetPositiveBut
g) tonText('Select')
app.makeToast(ms
ong) app = android.An
time.sleep(how_l droid()
app.dialogSetNegativeButtonText('Quit')
app.dialogSetSingleChoiceItems(athlete_names)
you are here 4 275
android query
app.dialogCreateAlert(list_title)
app.dialogSetSingleChoiceItems(athlete_names) Create a two-buttoned
dialog from the list of
app.dialogSetPositiveButtonText('Select')
athlete names.
app.dialogSetNegativeButtonText('Quit')
app.dialogShow()
Wait for the user to tap a ton
resp = app.dialogGetResponse().result then assign the result to “rebut
sp”.
,
276 Chapter 8
mobile app development
Test Drive
Recall that (for now) your Android Python scripts run within the emulator, not within IDLE. So use the
tools/adb program to copy your program to the emulator. Call your program coachapp.py. When
the code is copied over, start SL4A on your emulator, and then tap your script’s name.
This is looking really good! Your app has communicated with your web server,
requested and received the list of athlete names, and displayed the list on your
emulator.
If you app doesn’t run, don’t panic. Check your code for typos.
Run your app again in the Python terminal by tapping on the little terminal icon to the
left of the “run wheel” within SL4A. If your code raises an error, you’ll see any messages
on the emulator’s screen, which should give you a good idea of what went wrong.
Index item 0
Index item 1
Index item 2
Index item 3
Index item 4
So…if the positive button is tapped, you can index into the list of athlete names
to see which athlete was selected from the displayed list. The selected name can then
be sent to the web server to request the rest of the athlete’s data using the send_
to_server() function.
You can use this behavior in the next version of your code.
278 Chapter 8
mobile app development
Additionally, write the code required to display the list of times returned from the server within an Android
2 dialog.
Hints: Use the dialogSetItems() method from the Android API to add a list of items to a dialog. Also,
remember that the data arriving over the Internet will be formatted using JSON.
You were to assume that you have a CGI script called cgi-bin/
1
generate_data.py, which, when called requests the data for
a named athlete from the server.
Provide the name of You were to provide the code (which includes a call to
the CGI to run. the send_to_server() function) to implement this
functionality:
get_data_cgi = '/cgi-bin/generate_data.py'
Send the request send_to_server(web_server + get_data_cgi, {'which_athlete': which_athlete})
to the web server,
together with the
athlete name. Include the data.
Additionally, you were to write the code required to display the list of times returned from the server within
2 an Android dialog: Wh ich button was pressed?
280 Chapter 8
mobile app development
#! /usr/local/bin/python3
import cgi
import json
import athletemodel Get all the data
import yate from the model.
Process the
data sent with athletes = athletemodel.get_from_store()
the request and
extract the
form_data = cgi.FieldStorage() Start a web
athlete’s name. athlete_name = form_data['which_athlete'].value response, with JSON
print(yate.start_response('application/json')) as the data type.
print(json.dumps(athletes[athlete_name]))
import android
import json
import time
web_server = 'http://192.168.1.34:8080'
The rest of your
code is on the
get_names_cgi = '/cgi-bin/generate_names.py' following page.
get_data_cgi = '/cgi-bin/generate_data.py'
app = android.Android()
status_update(hello_msg)
app.dialogCreateAlert(list_title)
app.dialogSetSingleChoiceItems(athlete_names)
app.dialogSetPositiveButtonText('Select')
app.dialogSetNegativeButtonText('Quit')
app.dialogShow()
resp = app.dialogGetResponse().result
if resp['which'] in ('positive'):
selected_athlete = app.dialogGetSelectedItems().result[0]
which_athlete = athlete_names[selected_athlete]
athlete = json.loads(send_to_server(web_server + get_data_cgi,
{'which_athlete': which_athlete}))
status_update(quit_msg)
282 Chapter 8
mobile app development
Test Drive
Let’s give the latest version of your app a go. Copy the app to your emulator, and put the new CGI
script in your cgi-bin folder on your web server (remember to set the executable bit, if needed).
What happens when you run your latest app using the emulator’s Python shell as opposed to the
“run wheel”?
You’re getting a
“TypeError”.
Yikes! Your code has a TypeError, which is crashing your app when you try
to display the selected athlete’s timing data. Why do you think this is happening?
Let’s add a debugging line of code to your CGI script to try and determine what’s
going on. Recall that the CGI mechanism captures any output your script sends
to standard output by default, so let’s use code like this to send your debugging
messgage to the web server’s console, which is displaying on standard error: Redirect the output
from “print()” to
“stderr”, rather
Import “sys” import sys
than the default,
from the which is “stdout”.
standard library. print(json.dumps(athletes[athlete_name]), file=sys.stderr)
Run your app again and, of course, it’s still crashes with a TypeError.
However, if you check your web server’s console screen, you’ll see that the
data being sent as the JSON web response is clearly visible. Notice anything?
File Edit Window Help JustWhatsInTheData
$ python3 simple_httpd.py
Starting simple_httpd on port: 8080
This is a list of 192.168.1.33 - - [18/Sep/2010 17:40:04] "GET /cgi-bin/generate_names.py HTTP/1.1" 200 -
athlete timing 192.168.1.33 - - [18/Sep/2010 17:40:08] "POST /cgi-bin/generate_data.py HTTP/1.1" 200 -
values…but where’s ["2-44", "3:01", "2.44", "2.55", "2.51", "2:41", "2:41", "3:00", "2-32", "2.11", "2:26"]
284 Chapter 8
mobile app development
Let’s create a new method in your AthleteList class. Called to_dict(), your new
method needs to convert the class’s attribute data (name, DOB, and top3) into a dictionary. Be
sure to decorate your new method with @property, so that it appears to be a new attribute to
users of your class.
A: The @property decorator lets you specify that a method is to be presented to users of your class as if it were an attribute. If you
think about things, your to_dict() method doesn’t change the state of your object’s data in any way: it merely exists to return the object’s
attribute data as a dictionary. So, although to_dict() is a method, it behaves more like an attribute, and using the @property
decorator let’s you indicate this. Users of your class (that is, other programmers) don’t need to know that when they access the to_dict
attribute they are in fact running a method. All they see is a unified interface: attributes access your class’s data, while methods manipulate it.
Let’s create a new method in your AthleteList class. Called to_dict(), your new
method needs to convert the class’s attribute data (name, DOB, and top3) into a dictionary. Be
sure to decorate your new method with @property, so that it appears to be a new attribute to
users of your class.
Do this!
286 Chapter 8
mobile app development
Test Drive
With your changes applied to AthleteList.py, cgi-bin/generate_data.py and
coachapp.py, use the adb tool to copy the latest version of your app to the emulator. Let’s see
how things work now.
Tap!
Success.
288 Chapter 8
mobile app development
Configure AndFTP
With AndFTP running on your phone, configure it to connect to your
computer (Hostname) using SFTP as the transfer protocol (Type). Leave the Port,
Username, Password, and Remote dir entries as they are, but change the Local dir
entry to /sdcard/sl4a/scripts.
With the connection set up, tap AndFTP’s Connect button to establish a
connection to your SSH server, entering your Username and Password when
prompted.
With the connection to the server established, navigate to the server folder
containing the file(s) you want to transfer to the phone, mark the files for
download, and tap the Download button.
Your app
When the download completes, click Disconnect to terminate the connection is ready!
between the phone and your computer. If you transferred a Python program,
it should now be added to the list of scripts within SL4A.
290 Chapter 8
mobile app development
CHAPTER 8
belt and you’ve added some key
Python techiques to your toolbox.
th o n L i n g o
Py
The json library module lets you
s release
the previou
convert Python’s built-in types to the text-
• “Pyth
on 2” - has compatibility based JSON data interchange format.
which ot
of Python, Python 3 (and are n Use json.dumps() to create a
h
“issues” wit ting worked up over).
stringed version of a Python type.
Handling input
The Web and your phone are not just great ways to display data.
They are also great tools to for accepting input from your users. Of course, once your
webapp accepts data, it needs to put it somewhere, and the choices you make when
deciding what and where this “somewhere” is are often the difference between a webapp
that’s easy to grow and extend and one that isn’t. In this chapter, you’ll extend your webapp
to accept data from the Web (via a browser or from an Android phone), as well as look at
and enhance your back-end data-management services.
The National Underage Athletics Committee (NUAC) took one look at your
Android app and realized it’s just what they need…almost.
There are many ways to improve your webapp, but for now, let’s concentrate
on the committee’s most pressing need: adding a new time value to an existing
athlete’s data set.
Adding new data to text files isn’t going to work: there are just too many
coaches around the country adding data. The committee wants something
that’s user friendly from any web browser or Android phone.
294 Chapter 9
manage your data
On the Web, your user interacts with your web form and enters data. When
she presses the submit button, the web browser gathers up all of the form’s
data and sends it to the web server as part of the web request.
On your Android phone, you can use the dialogGetInput() method to
get input from the user, then mimic the behavior of the web form’s submit Here’s where the
button in code. data is included
In fact, you’ve done this already: check out this line of code from your
with the web
coachapp.py app, which sends the selected athlete name to your web
request.
server:
When your user clicks on the Send button, any data in the input area is sent
to the web server as part of the web request.
On your web server, you can access the CGI data using the facilities provided
by the standard library’s cgi module:
import cgi
The cgi module converts the data associated with the web request into a
dictionary-like object that you can then query to extract what you need.
296 Chapter 9
manage your data
Let’s turn the HTML form from the previous page into a template within the yate.py module.
1 Start by creating a new template called templates/form.html that allows you to
parameterize the form’s CGI script name, method, input tags, and submit button text:
With the template ready, write the code for two functions you intend to add to yate.py.
2
The first, called create_inputs(), takes a list of one of more strings and creates HTML <INPUT> tags
for each string, similar to the one that accepts TimeValue on the previous page.
The second, called do_form(), uses the template from Part 1 of this exercise together with the create_
inputs() function to generate a HTML form. Given a list of <INPUT>
def create_inputs(inputs_list): tag names.
Return the
generated tags to The name of the CGI script
the caller. return(html_inputs) <INPUT> tag names are requirand a list of
ed arguments.
def do_form(name, the_inputs, method="POST", text="Submit"):
Substitute the
arguments and
generated <INPUT>
tags into the return(form.substitute(cgi_name=name, http_method=method,
template to create
the form. list_of_inputs=inputs, submit_text=text))
You were to turn the HTML form into a template within the yate.py module.
1 You were to start by creating a new template called templates/form.html that allows you
to parameterize the form’s CGI script name, method, input tags, and submit button text.
return(html_inputs)
298 Chapter 9
manage your data
Test Drive
Here’s the code to a CGI script called cgi-bin/test-form.py, which generates the HTML form
from earlier. As you can see, there’s nothing to it.
#! /usr/local/bin/python3
Always start with a
CGI response. Dynamically create the form, ed.
import yate supplying any arguments as requir
print(yate.start_response('text/html'))
print(yate.do_form('add_timing_data.py', ['TimeValue'], text='Send'))
Set the executable bit (if required on your OS) using chmod + x test_form.py , and then use
your browser to confirm that your HTML form-generating code is working.
Additional information about the web request is also available to you via the
web server’s environment. Typically, you won’t need to access or use this data
directly. However, occasionally, it can be useful to report on some of it.
Here is some code that takes advantage of Python’s built-in support for
querying your CGI script’s environment using the os library, assuming the
environment values have been set by a friendly web server. Note that the data
in the enviroment is available to your code as a dictionary.
addr = os.environ['REMOTE_ADDR']
Query three environment variab les
les.
host = os.environ['REMOTE_HOST']
and assign their values to variab
method = os.environ['REQUEST_METHOD']
on standard
error.
Let’s exploit both code snippets on this page to log the data sent from a form
to your web server’s console. When you are convinced that the data is arriving
at your web server intact, you can extend your code to store the received data
in your model.
300 Chapter 9
manage your data
CGI Magnets
You need a new CGI script called add_timing_data.py,
which processes the data from a form and displays the data on
your web server’s console screen. The CGI needs to query the
environment, arranging to display the logged data on one line.
The code exists, but most of it is all over the floor. Rearrange the
magnets to produce a working program.
Don’t forget
this line if you #! /usr/local/bin/python3
are running on
Max OS X or import cgi
There’s not much of a
Linux. import os response for now…so
import time just send back plain
import sys text to the waiting
import yate
web browser.
print(yate.start_response('text/plain'))
There’s really addr = os.environ['REMOTE_ADDR']
nothing new host = os.environ['REMOTE_HOST']
here.
method = os.environ['REQUEST_METHOD']
cur_time = time.asctime(time.localtime())
print(host + ", " + addr + ", " + cur_time + ": " + method + ": ",
end='', file=sys.stderr)
print('OK.')
form = cgi.FieldStorage()
end=' ',
print(each_form_item + '->' + form[each_form_item].value,
print(file=sys.stderr)
file=sys.s
for each_form_item in form.keys(): tderr)
#! /usr/local/bin/python3
import cgi
import os
import time
import sys
import yate
print(yate.start_response('text/plain'))
addr = os.environ['REMOTE_ADDR']
host = os.environ['REMOTE_HOST']
method = os.environ['REQUEST_METHOD']
cur_time = time.asctime(time.localtime())
print(host + ", " + addr + ", " + cur_time + ": " + method + ": ",
end='', file=sys.stderr) Ensure that this “print()”
form = cgi.FieldStorage() function does NOT take a
newline.
for each_form_item in form.keys():
print(file=sys.stderr)
print('OK.')
Take a newline on standard error.
302 Chapter 9
manage your data
Test Drive
Let’s use your form-generating CGI script from earlier to try out add_timing_data.py. As you
enter data in the form and press the Send button, watch what happens on the web server’s console.
Enter some
data into your
web form.
All is “OK”.
Starting simple_httpd on port: 8080
localhost - - [21/Sep/2010 17:34:54] "GET /cgi-bin/test_form.py HTTP/1.1" 200 -
localhost - - [21/Sep/2010 17:34:54] "GET /favicon.ico HTTP/1.1" 200 -
localhost - - [21/Sep/2010 17:35:24] "POST /cgi-bin/add_timing_data.py HTTP/1.1" 200 -
localhost, 127.0.0.1, Tue Sep 21 17:35:24 2010: POST: TimeValue->2.22
That worked perfectly. The data entered into the form is delivered to your
CGI script on the your server. Your next challenge is to provide the same user
input experience on an Android phone.
A “Cancel” button
An “OK” button lets you change your
confirms the entry. mind.
A single Android call creates this interface for you using the
dialogGetInput() method:
Pressing the Ok button sets resp to the data entered into the input area. The result of your
Pressing the Cancel button sets resp to None, which is Python’s internal
user’s interaction
null-value.
with the dialog is
assigned to “resp”.
Let’s create some Android data-entry dialogs.
304 Chapter 9
manage your data
Let’s create a small Android app that interacts with your user twice. The first dialog asks the
user to confirm the web address and port to use for the web server. Assuming your user taps
the OK button on your dialog, a second dialog pops up to request the timing value to send to the
server. As with the first dialog, tapping the OK button continues execution by sending the newly
acquired timing value to the web server. Tapping Cancel at any time causes your app to exit.
Some of the code is provided for you. Your job is to complete the program. Write the code you
think you need under this code, and call your program get2inputsapp.py:
import android
from urllib import urlencode
from urllib2 import urlopen
There’s
nothing new server_title = 'Which server should I use?’
here…you’ve server_msg = "Please confirm the server address/name to use for your athlete's timing data:"
app = android.Android()
You were to create a small Android app that interacts with your user twice. The first dialog asks
the user to confirm the web address and port to use for the web server. Assuming your user taps
the OK button on your dialog, a second dialog pops up to request the timing value to send to the
server. As with the first dialog, tapping the OK button continues execution by sending the newly
acquired timing value to the web server. Tapping Cancel at any time causes your app to exit.
Some of the code was provided for you. Your job was to complete the program by writing the
code you think you need under this code and call your program get2inputsapp.py.
import android
from urllib import urlencode
from urllib2 import urlopen
app = android.Android()
306 Chapter 9
manage your data
Test Drive
Let’s copy get2inputsapp.py to the emulator using the adb tool:
The get2inputsapp.py app appears on the list of scripts within SL4A. Go ahead and give it a tap:
Web
Server
[‘Is this the right room
for an argument?’, “No
you haven’t!”, ‘When?’,
“No you didn’t!”, “You
didn’t!”, ‘You did not!’,
‘Ah! (taking out his wallet
and paying) Just the five
minutes.’, ‘You most
certainly did not!’, “Oh
no you didn’t!”, “Oh no
you didn’t!”, “Oh look,
this isn’t an argument!”,
“No it isn’t!”, “It’s
just contradiction!”,
‘It IS!’, ‘You just
contradicted me!’, ‘You
DID!’, ‘You did just
then!’, ‘(exasperated)
Oh, this is futile!!’,
‘Yes it is!’]
Of course...I could
write to the text file
and then immediately call
“put_to_store()” to update
the pickle, right?
Your up-to-
then!’, ‘(exasperated)
Oh, this is futile!!’,
‘Yes it is!’]
Your inconsistent
and upset pickle file
Web
Server
Q: Surely you should have thought about this problem long Q: So I’m facing a rewrite of large chunks of my code?
A:
ago and designed this “properly” from the start?
310 Chapter 9
manage your data
All of these fine technologies will work, but they are overkill for your app’s
data requirements. And besides some of these are way beyond the NUAC’s
budget, let alone their ability to set up, run, and maintain such a system.
What you need is something that’s effectively hidden from the NUAC yet lets
you take advantage of what a database management system has to offer.
312 Chapter 9
manage your data
Geek Bits
Connect
Establish a connection to your
chosen database back end.
Create
Create a cursor to communicate through the
connecton to your data.
Interact
Using the cursor, manipulate your
data using SQL.
Commit Rollback
Tell your connection to apply Tell your connection to abort your
all of your SQL manipulations Poof! SQL manipulations, returning your
to your data and make them data to the state it was in before your
permanent. interactions started.
Close
Destroy the connection to the
When you close your
database back end.
connection, your cursor
is destroyed, too.
314 Chapter 9
manage your data
Establish a connection
to a database. connection = sqlite3.connect('test.sqlite')
cursor = connection.cursor()
Create a cursor to
the data.
Execute some SQL. cursor.execute("""SELECT DATE('NOW')""")
Depending on what happens during the Interact phase of the process, you
either make any changes to your data permanent (commit) or decide to
abort your changes (rollback).
You can include code like this in your program. It is also possible to interact
with you SQLite data from within IDLE’s shell. Whichever option you choose,
you are interacting with your database using Python.
It’s great that you can use a database to hold your data. But what schema
should you use? Should you use one table, or do you need more? What data
items go where? How will you design your database?
{ Sarah: AthleteList
James: AthleteList
Julie: AthleteList
Mikey: AthleteLi
st ... }
Sarah: AthleteList
With this arrangement, it is pretty obvious which name, date of birth, and list
of times is associated with which individual athlete. But how do you model
these relationships within a SQL-compliant database system like SQLite?
316 Chapter 9
manage your data
e two
Note how this schema “links” th
tables using a foreign key.
There can be one and only one row of data for each athlete in the athletes
table. For each athlete, the value of id is guaranteed to be unique, which
ensures that two (or more) athletes with the same name are kept separate
within the system, because that have different ID values.
Within the timing_data table, each athlete can have any number of time
values associated with their unique athlete_id, with an individual row of
data for each recorded time.
If you create these two tables then arrange for your data to be inserted into
them, the NUAC’s data would be in a format that should make it easier to
work with.
Looking at the tables, it is easy to see how to add a new timing value for an
athlete. Simply add another row of data to the timing_data table.
Need to add an athlete? Add a row of data to the athletes table.
Want to know the fastest time? Extract the smallest value from the
timing_data table’s value column?
SQLite Magnets
Let’s create a small Python program that creates the coachdata.
sqlite database with the empty athletes and timing_data
tables. Call your program createDBtables.py. The code you
need is almost ready. Rearrange the magnets at the bottom of the
page to complete it.
import sqlite3
connection.commit()
connection.close()
cursor = connec
tion.cursor()
dob DATE NOT NU cursor.execute("
LL )""")
""CREATE TABLE
timing_data (
import sqlite3
connection = sqlite3.connect('coachdata.sqlite')
cursor = connection.cursor()
connection.commit()
The commit isn’t always required
connection.close()
most other database systems, butwith
is with SQLite. it
320 Chapter 9
manage your data
Connect
import sqlite3
to the new
database. connection = sqlite3.connect('coachdata.sqlite')
cursor = connection.cursor()
Grab the
data from import glob
the existing import athletemodel
model. data_files = glob.glob("../data/*.txt")
athletes = athletemodel.put_to_store(data_files)
If the query succeeds and returns data, it gets added to your cursor. You can
call a number of methods on your cursor to access the results:
• cursor.fetchone() returns the next row of data. Each of these cursor
• cursor.fetchmany() returns multiple rows of data. methods return a list
of rows.
• cursor.fetchall() returns all of the data.
Web
Server
322 Chapter 9
manage your data
Grab your pencil and write the lines of code needed to query the
athletes table for an athlete’s name and DOB, assigning the
result to a variable called the_current_id. Write another
your
Again, it’s OK to assume in “do query to extract the athlete’s times from the pickle and add them
code that the “name” and b” to the timing_data table.
variables exist and have values
assigned to them.
You were to grab your pencil and write the lines of code needed
to query the athletes table for an athlete’s name and DOB,
assigning the result to a variable called the_current_id. You
were then to write another query to extract the athlete’s times
from the pickle and add them to the timing_data table.
Query the “athletes”
table for the ID.
cursor.execute(“SELECT id from athletes WHERE name=? AND dob=?”,
Remember: (name, dob))
“fetchone()” It often makes sense
returns a list. to split your execute
statement over multiple
the_current_id = cursor.fetchone()[0] lines.
Take each of
the “clean” for each_time in athletes[each_ath].clean_data:
times and use cursor.execute("INSERT INTO timing_data (athlete_id, value) VALUES (?, ?)”,
it, together
with the ID, (the_current_id, each_time))
within the
SQL “INSERT” Add the ID and the time
statement. As always, make the value to the “timing_
connection.commit() change(s) permanent. data” table.
Do this!
That’s enough coding (for now). Let’s transfer your pickled data.
324 Chapter 9
manage your data
Test Drive
You’ve got two programs to run now: createDBtables.py creates an empty database, defining
the two tables, and initDBtables.py extracts the data from your pickle and populates the tables.
Rather than running these programs within IDLE, let’s use the Python command-line tool instead.
But how do you integrate your new database into your webapp?
326 Chapter 9
manage your data
Let’s spend some time amending your model code to use your SQLite database as opposed
to your pickle. Start with the code to your athletemodel.py module. Take a pencil and
strike out the lines of code you no longer need.
import pickle
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
templ = data.strip().split(',')
return(AthleteList(templ.pop(0), templ.pop(0), templ))
except IOError as ioerr:
print('File error (get_coach_data): ' + str(ioerr))
return(None)
def put_to_store(files_list):
all_athletes = {}
for each_file in files_list:
ath = get_coach_data(each_file)
all_athletes[ath.name] = ath
try:
with open('athletes.pickle', 'wb') as athf:
pickle.dump(all_athletes, athf)
except IOError as ioerr:
print('File error (put_and_store): ' + str(ioerr))
return(all_athletes)
328 Chapter 9
manage your data
def get_from_store():
all_athletes = {}
try:
with open('athletes.pickle', 'rb') as athf:
all_athletes = pickle.load(athf)
except IOError as ioerr:
print('File error (get_from_store): ' + str(ioerr))
return(all_athletes)
def get_names_from_store():
athletes = get_from_store()
response = [athletes[each_ath].name for each_ath in athletes]
return(response)
Remember: there’s no
requirement to maintain
the existing API.
Let’s spend some time amending your model code to use your SQLite database as opposed
to your pickle. Start with the code to your athletemodel.py module. You were to take a
pencil and strike out the lines of code you no longer need.
import pickle
def get_coach_data(filename):
try:
with open(filename) as f:
data = f.readline()
templ = data.strip().split(',') None of this code
return(AthleteList(templ.pop(0), templ.pop(0), templ)) is needed anymore,
because SQLite
provides the data
except IOError as ioerr:
print('File error (get_coach_data): ' + str(ioerr)) model for you.
return(None)
def put_to_store(files_list):
all_athletes = {}
for each_file in files_list:
ath = get_coach_data(each_file)
all_athletes[ath.name] = ath
try:
with open('athletes.pickle', 'wb') as athf:
pickle.dump(all_athletes, athf)
except IOError as ioerr:
print('File error (put_and_store): ' + str(ioerr))
return(all_athletes)
330 Chapter 9
manage your data
def get_from_store():
all_athletes = {}
try:
with open('athletes.pickle', 'rb') as athf:
all_athletes = pickle.load(athf)
except IOError as ioerr:
print('File error (get_from_store): ' + str(ioerr))
return(all_athletes)
def get_names_from_store():
athletes = get_from_store()
response = [athletes[each_ath].name for each_ath in athletes]
return(response)
import sqlite3
db_name = 'coachdata.sqlite'
Connect to the
database.
def get_names_from_store():
connection = sqlite3.connect(db_name)
Extract the cursor = connection.cursor()
data you need. results = cursor.execute("""SELECT name FROM athletes""")
response = [row[0] for row in results.fetchall()]
Formulate a connection.close()
response.
return(response)
332 Chapter 9
manage your data
Ready Bake Here’s the code for another new function called
Python Code get_athlete_from_id():
334 Chapter 9
manage your data
Here is the current code for your get_names_from_store() function. Rather than
amending this code, create a new function, called get_namesID_from_store(),
based on this code but including the ID values as well as the athlete names in its response.
Write your new function in the space provided.
import sqlite3
db_name = 'coachdata.sqlite'
def get_names_from_store():
connection = sqlite3.connect(db_name)
cursor = connection.cursor()
results = cursor.execute("""SELECT name FROM athletes""")
response = [row[0] for row in results.fetchall()]
connection.close()
return(response)
Here is your current code for your get_names_from_store() function. Rather than
amending this code, you were to create a new function, called get_namesID_from_
store(), based on this code but including the ID values as well as the athlete names in its
response. You were to write your new function in the space provided.
import sqlite3
db_name = 'coachdata.sqlite'
def get_names_from_store():
connection = sqlite3.connect(db_name)
cursor = connection.cursor()
results = cursor.execute("""SELECT name FROM athletes""")
response = [row[0] for row in results.fetchall()]
connection.close()
return(response)
336 Chapter 9
manage your data
Part 1: With your model code ready, let’s revisit each of your
CGI scripts to change them to support your new model. At the
import glob
import athletemodel
import yate
data_files = glob.glob("data/*.txt")
athletes = athletemodel.put_to_store(data_files)
athletes = athletemodel.get_from_store()
form_data = cgi.FieldStorage()
athlete_name = form_data['which_athlete'].value
an ge.
Another title ch
print(yate.start_response())
print(yate.include_header("NUAC's Timing Data"))
print(yate.header("Athlete: " + athlete_name + ", DOB: " + athletes[athlete_name].dob + "."))
print(yate.para("The top times for this athlete are:"))
print(yate.u_list(athletes[athlete_name].top3))
print(yate.para("The entire set of timing data is: " + str(athletes[athlete_name].clean_data) +
" (duplicates removed)."))
print(yate.include_footer({"Home": "/index.html", "Select another athlete": "generate_list.py"}))
Part 2: You’re not done with that pencil just yet! In addition to
amending the code to the CGIs that support your web browser’s
UI, you also need to change the CGIs that provide your webapp
data to your Android app. Amend these CGIs, too.
This is the
“generate_names.py”
CGI.
#! /usr/local/bin/python3
import json
import athletemodel
import yate
names = athletemodel.get_names_from_store()
print(yate.start_response('application/json'))
print(json.dumps(sorted(names)))
import cgi
import json
import sys
import athletemodel
import yate
athletes = athletemodel.get_from_store()
form_data = cgi.FieldStorage()
athlete_name = form_data['which_athlete'].value
print(yate.start_response('application/json'))
print(json.dumps(athletes[athlete_name].as_dict))
338 Chapter 9
manage your data
Part 1: With your model code ready, you were to revisit each of
your CGI scripts to change them to support your new model. At the
data_files = glob.glob("data/*.txt")
get_namesID_from_store()
athletes = athletemodel.put_to_store(data_files)
Part 2: You weren’t done with that pencil just yet! In addition to
amending the code to the CGIs that support your web browser’s
This is the UI, you also needed to change the CGIs that provide your webapp
“generate_names.py” data to your Android app. You were to amend these CGIs, too.
CGI.
#! /usr/local/bin/python3
import json
import athletemodel
import yate
get_namesID_from_store()
names = athletemodel.get_names_from_store()
print(yate.start_response(‘application/json’))
print(json.dumps(sorted(names)))
import athletemodel
import yate
athletes = athletemodel.get_from_store()
form_data = cgi.FieldStorage()
athlete_name = form_data[‘which_athlete’].value
athlete = athletemodel.get_athlete_from_id(athlete_id)
print(yate.start_response(‘application/json’))
print(json.dumps(athletes[athlete_name].as_dict))
340 Chapter 9
manage your data
Test Drive
Before you run your amended webapp, be sure to move you SQLite database into the top-level
directory of your webapp (that is, into the same folder your index.html file). That way, your
model code can find it, so move it into your webapp’s root folder now. When you are ready, take your
Start (or SQL-powered webapp for a spin.
restart) your
web server. File Edit Window Help StartYourWebEngine
$ python3 simple_httpd.py
Starting simple_httpd on port: 8080
Click on the
link on the home
page.
Display the list
of athlete names
And there’s as radio buttons.
Sally’s timing
data.
This is weird…instead of
the names, your app is
displaying a list of lists!
Just like with the CGI scripts, you need to amend you Android app to work
with the data that’s now arriving from your web server—that is, a list of lists
as opposed to a list.
342 Chapter 9
manage your data
Here is your current coachapp.py code, which you need to amend to support the way your
webapp’s model now works. Grab a pencil and make the necessary changes to this code.
app = android.Android()
status_update(hello_msg)
athlete_names = sorted(json.loads(send_to_server(web_server + get_names_cgi)))
app.dialogCreateAlert(list_title)
app.dialogSetSingleChoiceItems(athlete_names)
app.dialogSetPositiveButtonText('Select')
app.dialogSetNegativeButtonText('Quit')
app.dialogShow()
resp = app.dialogGetResponse().result
if resp['which'] in ('positive'):
selected_athlete = app.dialogGetSelectedItems().result[0]
which_athlete = athlete_names[selected_athlete]
athlete = json.loads(send_to_server(web_server + get_data_cgi,{'which_athlete': which_athlete}))
athlete_title = athlete['Name'] + ' (' + athlete['DOB'] + '), top 3 times:'
app.dialogCreateAlert(athlete_title)
app.dialogSetItems(athlete['Top3'])
app.dialogSetPositiveButtonText('OK')
app.dialogShow()
resp = app.dialogGetResponse().result
status_update(quit_msg)
Here is your current coachapp.py code, which you need to amend to support the way your
webapp’s model now works. You were to grab a pencil and make the necessary changes to this
code.
app = android.Android()
Extract the athlete
names ONLY from the
list of lists.
def status_update(msg, how_long=2):
# There is no change to this code from the previous chapter.
status_update(hello_msg) athletes =
athlete_names = sorted(json.loads(send_to_server(web_server + get_names_cgi)))
app.dialogCreateAlert(list_title) athlete_names = [ath[0] for ath in athletes]
app.dialogSetSingleChoiceItems(athlete_names)
app.dialogSetPositiveButtonText('Select') This is a cool use of a
app.dialogSetNegativeButtonText('Quit') comprehension.
app.dialogShow()
resp = app.dialogGetResponse().result
Determine the ID associated
if resp['which'] in ('positive'): with the selected athlete.
selected_athlete = app.dialogGetSelectedItems().result[0]
which_athlete = athlete_names[selected_athlete] athletes[selected_athlete][1]
athlete = json.loads(send_to_server(web_server + get_data_cgi,{'which_athlete': which_athlete}))
athlete_title = athlete['Name'] + ' (' + athlete['DOB'] + '), top 3 times:'
app.dialogCreateAlert(athlete_title)
app.dialogSetItems(athlete['Top3']) athlete[‘top3']
app.dialogSetPositiveButtonText('OK') A small adjustment to next
app.dialogShow() line is needed to access the
resp = app.dialogGetResponse().result “top3” attribute.
status_update(quit_msg)
344 Chapter 9
manage your data
...
d,
ton that’s tappe
Based on the hibutng (“pass”) or t
st ar a
if resp['which'] in ('positive'):
eit he r d o no t
the user.
new dialog with
pass
elif resp['which'] in ('negative'):
app.dialogSetNegativeButtonText('Add Time')
...
if resp['which'] in ('positive'):
pass
elif resp['which'] in ('negative'): Define the dialog’s
timing_title = 'Enter a new time' titles and specify
timing_msg = 'Provide a new timing value ' + athlete['Name'] + ': ' the CGI to send
add_time_cgi = '/cgi-bin/add_timing_data.py' the data to.
346 Chapter 9
manage your data
Test Drive
Use the tools/adb command to copy your latest app to the emulator, and give your app a go.
Select “Vera”
from the list
of athletes…
…to see Vera’s top 3
times, then tap the
“Add Time” button…
…to enter a new
time, which is
then sent to
your web server.
Android app to
your web server.
With this version of your CGI script running on your web server, any new
times entered by anyone on an Android phone are added to the data in the
database.
The NUAC no longer has to worry about adding data to text files, because
the files are effectively obsoleted by the use of SQLite.
You’ve produced a robust solution that is more manageable, scalable,
programmable, and extendable. And it’s all thanks to the power of Python,
it’s database API and the inclusion of sqlite3 in the standard library.
348 Chapter 9
manage your data
To answer these and other queries on the data in the NUAC’s database, you’ll
have to bone up on your SQL. Then it’s up to you to take it from there.
You’ve converted your webapp to use an SQL database. As your data
management needs increase, you can consider alternative heavy-duty data
management technologies as needed.
This is great work. Your webapp is ready for the big time.
Py -a
API”
from within your CGI script.
350 Chapter 9
10 scaling your webapp
Getting real
The Web is a great place to host your app…until things get real.
Sooner or later, you’ll hit the jackpot and your webapp will be wildly successful. When that
happens, your webapp goes from a handful of hits a day to thousands, possibly ten of
thousands, or even more. Will you be ready? Will your web server handle the load? How
will you know? What will it cost? Who will pay? Can your data model scale to millions
upon millions of data items without slowing to a crawl? Getting a webapp up and running
is easy with Python and now, thanks to Google App Engine, scaling a Python webapp is
achievable, too. So…flip the page and find out how.
After a busy sightings weekend, the central office is swamped with completed
forms for thousands of sightings…which is a data-entry nightmare as all those
forms can take an age to process manually. There’s nothing worse than being
stuck in front of your computer entering data when all you want to do is be
out on the water looking for humpbacks…
352 Chapter 10
scaling your webapp
We need to somehow
automate the recording Ideally, a solution that works on
Yeah, but we aren’t
of our sightings... the Web would be great. That
technical, and we don’t
way, anyone from anywhere
have much money.
could record a sighting. Look!
There’s one...
354 Chapter 10
scaling your webapp
python2.5 -V
If this command gives an error, pop on over to the Python website and grab
the 2.5 release for your operating system.
Q: Aren’t things going backward here? First, there was Python 3, then it was Python 2.6 for Android, and now we are dropping
down to 2.5 for App Engine? What gives?
A: That’s a great question. It’s important to remember to always code to the restrictions placed on you.You might think that it sucks that GAE
runs on Python 2.5, but you shouldn’t. Think of it as just another restriction placed on the code you write—that is, it must target Release 2.5
of Python. As with the Android code you created in the previous chapters, the GAE code you are about to write is not all that different than the
Python code for 3. In fact, you will be hard pressed to spot the difference.
Do this! sayhello.py
mygaetest
356 Chapter 10
scaling your webapp
Test Drive
The GAE SDK includes a test web server, so let’s use it to take your test GAE webapp for a spin. If you
are running on Windows or Mac OS X, fire up the Google App Engine Launcher front end. This tool
Click this makes it easy to start, stop, and monitor your webapp. On Linux, you’ll need to invoke a command
button to to kick things off. If you are using the GAE Launcher, choose File -> Add Existing Application from
the menu system to browse and select your webapp’s folder. Also: be sure to edit the Launcher’s
start your Preferences to select Python 2.5 as your preferred Python Path.
webapp.
358 Chapter 10
scaling your webapp
360 Chapter 10
scaling your webapp
Pool Puzzle
Your job is to take the properties from the
pool and place them in the correct
place in the class code, which is in a
file called hfwwgDB.py. Your goal
is to assign the correct property type
to each of the attributes within your
Sighting class.
)
db.StringProperty(
db.StringProperty(
)
db.DateProperty()
db.TimeProperty()
db.StringProperty() )
db.StringProperty(
)
db.StringProperty(
) db.StringProperty(
db.StringProperty(
)
class Sighting(db.Model):
name = db.StringProperty()
email = db.StringProperty()
date = db.DateProperty() Everything is a
db.TimeProperty()
“StringProperty”,
time =
except the “date” and
location = db.StringProperty() “time” fields.
fin_type = db.StringProperty()
whale_type = db.StringProperty()
blow_type = db.StringProperty()
wave_type = db.StringProperty()
362 Chapter 10
scaling your webapp
header.html <html>
{{ links }}
</p>
<head> </body>
<title>{{ title </html>
}}</title>
</head>
<body>
<h1>{{ title }}</
h1> <form method="PO
ST" action="/">
<table>
form_start.html
form_end.html sub_title }}"></td>
</tr>
><td><input type="submit" value="{{
<tr><th> </th
</table>
</form>
A: You could, if you want. However, if you build up your view from snippets of HTML in templates, you open up the possibility of reusing those
HTML snippets in lots of places. For instance, to maintain a consistent look and feel, you can use the same header and footer template on all of
your web pages, assuming of course that your header and footer aren’t already embedded in an entire web page (which can’t be reused).
364 Chapter 10
scaling your webapp
1 Let’s write the rest of the code needed to create a view that
displays a data entry form for your HFWWG webapp.
In addition to your web page header code (which already exists
and is provided for you), you need to write code that starts a new
form, displays the form fields, terminates the form with a submit
button, and then finishes off the web page. Make use of the
templates you’ve been given and (here’s the rub) do it all in no
Remember: no
more than 4
lines of code!
2 Now that you have attempted to write the code required in no more than four lines of code, what
problem(s) have you encountered. In the space below, note down any issue(s) you are having.
1 You were to write the rest of the code needed to create a view
that displays a data entry form for your HFWWG webapp.
In addition to your webpage header code (which already exists
and is provided for you), you were to write code with starts a
new form, displays the form fields, terminates the form which a
submit button, then finishes off the webpage. You were to make
use of the templates you’ve been given and (here’s the rub) you
had to do it all in no more than four more lines of code.
This is an issue,
isn’t it? # We need to generate the FORM fields in here…but how?!?
2 Having attempted to write the code required in no more than four lines of code, you were to make a
note of any issue(s) you encountered.
366 Chapter 10
scaling your webapp
called “birthDB.py”.
class BirthDetails(db.Model):
name = db.StringProperty()
date_of_birth = db.DateProperty()
time_of_birth = db.TimeProperty()
This model is used with Django’s framework to generate the HTML markup
needed to render the data-entry form. All you need to do is inherit from a
GAE-included class called djangoforms.ModelForm:
ion
Import the forms library in addit
from google.appengine.ext.webapp import template
in just
som e cod e mis sin g fro m here… but don’t worry: you’ll get toweiten the
There is trate on understanding the link s bet
a moment. For now, just concen ango form validation fra me work.
model, the view code, and the Dj
368 Chapter 10
scaling your webapp
Use the View Source menu option within your web browser to inspect the
HTML markup generated.
enough to create
The Django framework is smyouart input fields (based on
sensible labels for each of l).r
the names used in your mode
It’s time to tie things all together with your controller code.
to be
Your top-level folder needs ati line
named to match the “applic file.on” Put all of your webapp’s
in your webapp’s “app.yaml” hfwwgapp
controller code and
configuration files in here.
As you’ve seen, any CGI can run on GAE, but to get the most out of Google’s
technology, you need to code to the WSGI standard. Here’s some boilerplate Import a utility that
code that every WSGI-compatible GAE webapp starts with: runs your webapp.
370 Chapter 10
scaling your webapp
attention. There’s no
guiding lines on the
fridge door.
data
from google.appengine.ext.webapp import template
from google.appengine.ext.db import djangoforms
Import your GAE
model code.
import hfwwgDB
def main():
run_wsgi_app(app)
if __name__ == '__main__':
main()
372 Chapter 10
scaling your webapp
Test Drive
It’s been a long time coming, but you are now ready to test the first version of your sightings form.
If you haven’t done so already, create an app.yaml file, too. Set the application line to hfwwg
and the script line to hfwwg.py. One final step is to use the Add Existing Application menu option
within the GAE Launcher to select your top-level folder as the location of your webapp.
Test Drive
With your stylesheets in place and your app.yaml file amended, ask your browser to reload your form.
Looking good.
376 Chapter 10
scaling your webapp
Test Drive
With these changes applied to your model code, refresh your web browser once more.
Your form now looks great! Go ahead and enter some test data, and then
press the Submit Sighting button.
What happens?
Whoops…that’s not
exactly user-friendly.
378 Chapter 10
scaling your webapp
e value associated
The “get()” method returnsldthnam e.
with the provided form fie
So…if you know the name of your form field, you can access its value from
within your webapp using the self.request.get() method.
But what do you do with the data once you have it?
called “birthDB.py”.
class BirthDetails(db.Model):
name = db.StringProperty()
date_of_birth = db.DateProperty()
time_of_birth = db.TimeProperty()
Assume that an HTML form has sent data to your webapp. The data is
destined to be stored in the GAE datastore. Here’s some code to do the heavy
lifting:
There’s nothing to it: create a new object from your data model, get the
data from your HTML form, assign it to the object’s attributes, and then use
the put() method to save your data in the datastore.
380 Chapter 10
scaling your webapp
Based on what you know about how to put your HTML form’s data into the GAE datastore,
create the code for the post() method that your webapp now needs. Some of the code has
been done for you already. You are to provide the rest.
def post(self):
html = template.render('templates/header.html',
{'title': 'Thank you!'})
html = html + "<p>Thank you for providing your sighting data.</p>"
html = html + template.render('templates/footer.html',
{'links': 'Enter <a href="/">another sighting</a>.'})
self.response.out.write(html)
Based on what you know about how to put your HTML form’s data into the GAE datastore, you
were to create the code for the post() method that your webapp now needs. Some of the
code has been done for you already. You were to provide the rest.
new_sighting = hfwwgDB.Sighting()
new_sighting.name = self.request.get(‘name’)
new_sighting.email = self.request.get(‘email’)
new_sighting.date = self.request.get(‘date’)
For each of the data
new_sighting.time = self.request.get(‘time’) values received from
the HTML form,
new_sighting.location = self.request.get(‘location’) assign them to the
attributes of the
new_sighting.fin_type = self.request.get(‘fin_type’) newly created object.
new_sighting.whale_type = self.request.get(‘whale_type’)
new_sighting.blow_type =self.request.get(‘blow_type’)
new_sighting.wave_type = self.request.get(‘wave_type’)
e GAE
new_sighting.put() Store your populated object in th
datastore.
html = template.render('templates/header.html',
{'title': 'Thank you!'})
html = html + "<p>Thank you for providing your sighting data.</p>"
html = html + template.render('templates/footer.html',
{'links': 'Enter <a href="/">another sighting</a>.'})
self.response.out.write(html)
382 Chapter 10
scaling your webapp
Test Drive
Add your post() code to your webapp (within the hfwwg.py file) and press the Back button on
your web browser. Click the Submit Sighting button once more and see what happens this time.
Here’s your
form with the
data waiting to
be submitted.
The trouble is, when it comes to dates and times, there are lots of ways to
specify values.
384 Chapter 10
scaling your webapp
...
date = db.StringProperty() It’s a small change,
time = db.StringProperty() but it’ll make all the
difference.
...
Test Drive
Change the types of date and time within htwwgDB.py to db.StringProperty(), being
sure to save the file once you’ve made your edit. Click Back in your web brwoser and submit your
OK, folks… sightings data once more.
let’s try
this again.
Success! It appears to
have worked this time.
386 Chapter 10
scaling your webapp
With a few sightings entered, let’s use App Engine’s included developer console to confirm that the
sightings are in the datastore.
388 Chapter 10
scaling your webapp
application: hfwwgapp
version: 1
runtime: python
api_version: 1
handlers:
- url: /static
static_dir: static
- url: /.*
script: hfwwg.py
That’s all there
login: required
is to it.
Now, when you try to access your webapp, you are asked to log in before proceeding.
Let’s ensure that Django’s form validation framework excludes this new attribute
when generating your HTML form. Within your hfwwg.py file, change your
SightingForm class to look like this:
class SightingForm(djangoforms.ModelForm):
Staying within your hfwwg.py file, add another import statement near the
top of your program: Import GAE’s
from google.appengine.api import users
Google Accounts
API.
In your post() method, right before you put your new sighting to the
datastore, add this line of code:
new_sighting.which_user = users.get_current_user()
390 Chapter 10
scaling your webapp
Assuming all went according to plan and GAE confirmed that your
application has been created, all that’s left to do is to deploy. Return to the
GAE Launcher and click on the Deploy button. The console displays a
bunch of status message while the deployment progresses. If all is well, you’ll
be told that “appcfg.py has finished with exit code 0”.
392 Chapter 10
scaling your webapp
After a successful login, your sighting form appears. Go ahead and enter some test data:
394 Chapter 10
scaling your webapp
CHAPTER 10
belt and you’ve added more great
Python technology to your ever-
expanding Python toolbox.
• “Datastore” - the
data repository used is based on the one use in the Django
tly Project.
by Google App Engine to permanen
store your data.
App Engine can also use Django’s Form
Validation Framework.
• “Entity” - the name
used for a “row of Use the self.response object to
data”. construct a GAE web response.
• “Property” - the na
me used for a “data Use the self.request object to
value”. access form data within a GAE webapp.
When responding to a GET request,
implement the required functionality in a
get() method.
When responding to a POST request,
implement the required functionality in a
post() method.
Store data in the App Engine datastore
using the put() method.
Data wrangling
Once I build up a head of
steam, it’s not all that hard
to keep on running, and
running, and running...
It’s great when you can apply Python to a specific domain area.
Whether it’s web development, database management, or mobile apps, Python helps you
get the job done by not getting in the way of you coding your solution. And then there’s
the other types of problems: the ones you can’t categorize or attach to a domain. Problems
that are in themselves so unique you have to look at them in a different, highly specific way.
Creating bespoke software solutions to these type of problems is an area where Python
excels. In this, your final chapter, you’ll stretch your Python skills to the limit and solve
problems along the way.
The timed
distance is 15km.
The predicted
marathon goal.
398 Chapter 11
dealing with complexity
Not to mention: forgetting the sheets, keeping the sheets up to date, and
having to flip back and forth through the sheets looking for a closest match.
Of course, word of your newly acquired Python programming skills is
getting around, especially among the running crowd. Ideally, the Marathon
Club needs an Android app that can be loaded onto a bunch of phones and
carried in each coach’s pocket. The app needs to automate the lookup and
distance predictions.
400 Chapter 11
dealing with complexity
V02,84.8,82.9,81.1,79.3,77.5,75.8,74.2,72.5,70.9,69.4,67.9,66.4,64.9,63.5,62.1,60.7,59.4,58.1,56.8,55.
2mi,8:00,8:10,8:21,8:33,8:44,8:56,9:08,9:20,9:33,9:46,9:59,10:13,10:26,10:41,10:55,11:10,11:25,11:40,1
5k,12:49,13:06,13:24,13:42,14:00,14:19,14:38,14:58,15:18,15:39,16:00,16:22,16:44,17:06,17:30,17:53,18:
You somehow have to model the data from the CSV file in your
Python program. Can you think of a data structure that might
help here? Justify your selection.
You somehow have to model the data from the CSV file in your
Python program. You were to think of a data structure that might
help here? You were also to justify your selection.
402 Chapter 11
dealing with complexity
Marathon Magnets
Here’s some code that reads the raw data from from the CSV data
file. The column headings from the first line are loaded into a list
called column_headings. The rest of the data (all the rows of
times) are loaded into a dictionary called row_data, with each row
of data keyed with the row label string from the start of each line.
Of course, as luck would have it, someone was cleaning the fridge
door, and they’ve left a bunch of magnets on the floor. See if you
can arrange the magnets into their correct order.
What needs
to go here?
Process the
“column_headings”
list here.
for each_line in paces:
Process
“row_data”
here.
num_cols = len(column_headings)
print(num_cols, end=' -> ')
With the data
print(column_headings) loaded, this code
lets you check if it’s
num_2mi = len(row_data['2mi']) all OK.
print(num_2mi, end=' -> ')
print(row_data['2mi'])
num_Marathon = len(row_data['Marathon'])
print(num_Marathon, end=' -> ')
print(row_data['Marathon'])
row = each_line.strip().split(',')
column_hea
dings = row_label
row_data[r = row.pop(
ow_label] 0)
= row
row_data = {}
paces.readline().strip().split(',') column_headings.pop(0)
num_2mi = len(row_data['2mi'])
print(num_2mi, end=' -> ')
print(row_data['2mi'])
num_Marathon = len(row_data['Marathon'])
print(num_Marathon, end=' -> ')
print(row_data['Marathon'])
404 Chapter 11
dealing with complexity
Test Drive
Load your code into IDLE and, with the CSV in the same folder as your code, run it to see what you
get on screen.
Your code
in IDLE.
The column
headings
The “2mi”
row of
data
The
“Marathon”
row of
data
That’s a great start: you’ve managed to read the data from the CSV and put
the headings into a list and the data into a dictionary.
What’s next?
406 Chapter 11
dealing with complexity
row_data['2mi']
={ 8:00 8:10 8:21
row_data = {}
with open('PaceData.csv') as paces:
No changes column_headings = paces.readline().strip().split(',')
are needed column_headings.pop(0)
here. for each_line in paces:
Let’s not hard-
code 50, calc the
row = each_line.strip().split(',') value instead. Associate the
row_label = row.pop(0) column heading
Create another, with the time
empty dictionary.
inner_dict = {}
value from the
for i in range(len(column_headings)): row.
inner_dict[row[i]] = column_headings[i]
row_data[row_label] = inner_dict
With each iteration,
“i” is the current
column number. With the dictionary populated, ass
label in “row_data”. ign it to its
Go ahead and add the extra dictionary populating code to your program. Let’s remove all of those print()
statements from the end of your program, because you’ll use the IDLE shell to test your code. Run the code by
pressing F5 or by selecting the Run Module option from the Run menu. Use the dir() BIF to confirm that your
program code executed and that a collection of variables have been created in Python’s namespace:
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'column_headings', 'each_line', 'i',
'inner_dict', 'paces', 'row', 'row_data', 'row_label']
All of your code’s variables exist.
Take another look at (part of ) the spreadsheet data file above, and let’s try and find the column heading
associated with the 43:24 time on the 15k row. Let’s then use the column heading to find the predicted time for a
20k run:
ed as “81.1”.
The associated column heading is correctly identifi
>>> column_heading
'81.1'
408 Chapter 11
dealing with complexity
Working out the predicted time in the 20k row of data involves finding the
key in the row’s dictionary whose value is set to the just-discovered value stored
in column_heading.
You are interested only in data
satisfies this conditional. that
.
This is the data you’re searching
A conditional list comprehension is put to good use here. Recall that the list
comprehension syntax is a shorthand notation for a for loop. The loop searches
through the data in the list of keys associated with the dictionary stored at
row_data['20k']. If the value associated with the key (in k) is the same
as column_heading, the value of k is added to the comprehensions results,
which are then assigned to a new list call predicton.
410 Chapter 11
dealing with complexity
412 Chapter 11
dealing with complexity
The entered data is assigned to “age” and >>> age = input('What is your age: ')
it’s a string, even though you might want to What is your age: 21
treat it like it’s a number. >>> age
'21'
[Editor’s note: Yeah…
>>> int(age) dream on, Paul. §]
Convert the input to the tytape. you 21
need BEFORE using the da
When your program runs, your user enters some data, and look what happens:
414 Chapter 11
dealing with complexity
The “find_closest”
function does a simple
linear search, returning
the value in “target_data”
Here’s an example of a nested that most closely matches
function, which is allowed the “look_for” argument.
in Python. Given two values,
this function returns the
difference between them.
416 Chapter 11
dealing with complexity
Let’s test the find_it.py module to try and determine if it meets the requirements of your application. Load
the module into IDLE and then press F5 or choose Run Module from the Run menu:
Let’s try it with some of data that more closely resembles your CSV data:
418 Chapter 11
dealing with complexity
Now that you have the tm2secs2tm.py and find_it.py modules, let’s create a function
that uses the facilities provided by these modules to solve your searching problem. Your new
function, called find_nearest_time(), takes two arguments: the time to look for and a
list of times to search. The function returns the closest time found as a string:
The code you from find_it import find_closest
need has been
started for
from tm2secs2tm import time2secs, secs2time
you.
def find_nearest_time(look_for, target_data):
Now that you have the tm2secs2tm.py and find_it.py modules, you were to create a
function that uses the facilities provided by these modules to solve your searching problem. Your
new function, called find_nearest_time(), takes two arguments: the time to look for and
a list of times to search. The function returns the closest time found as a string:
Import the
from find_it import find_closest
The function takes two
team’s code. from tm2secs2tm import time2secs, secs2time
arguments, a time string
and a list of time strings.
def find_nearest_time(look_for, target_data):
Convert the time string
you are looking for into its what = time2secs(look_for)
equivalent value in seconds. Convert the lines of time
where = [time2secs(t) for t in target_data] strings into seconds.
Call “find_closest()”, res = find_closest(what, where) Return the closest match to the calling
supplying the converted data. code, after converting it back to a time
return(secs2time(res))
string.
420 Chapter 11
dealing with complexity
Test Drive
With all this code available to you, it’s an easy exercise to put it all together in your program and
produce a complete solution to the Marathon Club’s prediction problem. Let’s take it for a test run.
same
Try out your program with the
data input from earlier.
Another “KeyError”…
After all that, you’re getting the same error as before. Bummer.
“HH:MM:SS” '01:01:45'
When your code takes one of these returned values and tries to index into
your dictionary, there’s no match found, because your dictionary’s keys do not
confirm to the HH:MM:SS format. The solution to this problem is to ensure
that every time you use a time-string in your code, make sure it’s in HH:MM:SS
format:
422 Chapter 11
dealing with complexity
Test Drive
Let’s try your code one more time. Hopefully, now that all of the time strings within the system
conform to HH:MM:SS format, your code will behave itself.
behaves
This time around, your program
itself and works fine.
This is working well. You’ve solved your application’s central problem: your
program reads in the spreadsheet data from the CSV file, turns it into a
dictionary of dictionaries, and lets you interact with your user to acquire
the recorded time at a particular distance before predicting a time for
another distance.
Not counting the code provided by the Head First Code Review Team,
you’ve written fewer than 40 lines of code to solve this problem. That’s
quite an achievement. All that’s left to do is to port your program to the club’s
Android’s phones.
Port to Android
Your code is working great. Now it’s time to port your text-based Python
program to Android. Most of your code doesn’t need to change, only the
parts that interact with your user.
Obviously, you’ll want to make things as easy to use as possible for users of
your latest Android app, providing an interface not unlike this one.
2. Enter the
1. Start by recorded time…
picking a distance
run…
3. Select a
distance to
predict to…
4. After the
lookup, display
the predicted
time.
This is a
“dialogSetItems”
dialog box.
424 Chapter 11
dealing with complexity
426 Chapter 11
dealing with complexity
status_update(hello_msg)
resp = do_dialog("Pick a distance", distances, )
predicted_distance =
predicted_distance = distances[predicted_distance]
prediction = [k for k in row_data[predicted_distance].keys()
if row_data[predicted_distance][k] == closest_column_heading]
do_dialog('The predicted time running ' + predicted_distance + ' is: ',
prediction, app.dialogSetItems, "OK", None)
status_update(quit_msg)
recorded_time
app.dialogGetSelec
tedItems().result[
SetSingleChoiceItems 0]
app.dialog te ms
eChoiceI
app.dialogSetSingl
if resp['which'] in ('positive'):
in ('positive'): if resp['which']
app.dialogGetSelec
recorded_time tedItems().result[
0]
428 Chapter 11
dealing with complexity
Do your imports.
process
Display your UI to your user and
the resulting interaction.
Test Drive
It’s time to test your Android app on the Android Emulator before loading a working application
onto a “real” phone. Start your Android emulator and begin by transferring your code and the
files it needs onto the emulator’s SDCARD. Use the adb command in the tools folder to copy
marathonapp.py, find_it.py, tm2sec2tm.py and PaceData.csv to the emulator, and
then take your app for a spin.
emulator with
these commands.
430 Chapter 11
dealing with complexity
Congratulations!
• List comprehension
s can be rewritten The time module, which is part of
as an equivalent “for” loop. the standard library, has a number of
functions that make converting between
time formats possible.
432 Chapter 11
dealing with complexity
The WingWare
Python IDE
More general tools also exist. If you are running Linux, the KDevelop IDE
integrates well with Python.
And, of course,there are all those programmer editors which are often all
you’ll ever need. Many Mac OS X programmers swear by the TextMate
programmer’s editor. There’s more than a few Python programmers using
emacs and vi (or its more common variant, vim). Your author is a huge fan
of vim, but also spends large portions of his day using IDLE and the Python
shell.
436 appendix
leftovers
A function which
attempts to read
from and write to
the global variable
called “name”.
Call the function.
If you try to run this program, Python complains with this error message:
UnboundLocalError: local variable ‘name’ referenced before assignment…whatever that
means!
When it comes to scope, Python is quite happy to let you access and read
the value of a global variable within a function, but you cannot change
it. When Python sees the assignment, it looks for a local variable called
name, doesn’t find it, and throws a hissy fit and an UnboundLocalError
exception. To access and change a global variable, you must explicitly declare
that’s your intention, as follows:
#3: Testing
Writing code is one thing, but testing it is quite another. The combination of
the Python shell and IDLE is great for testing and experimenting with small
snippets of code, but for anything substantial, a testing framework is a must.
Python comes with two testing frameworks out of the box.
The first is familiar to programmers coming from another modern language,
because it’s based on the popular xUnit testing framework. Python’s
unittest module (which is part of the standard library) lets you create test
code, test data, and a test suite for your modules. These exist in separate files
from you code and allow you to exercise your code in various ways. If you
already use a similar framework with your current language, rest assured that
Python’s implementation is essentially the same.
The other testing framework, called doctest, is also part of the standard
library. This framework allows you to take the output from a Python shell or
IDLE session and use it as a test. All you need to do is copy the content from
the shell and add it to your modules documentation strings. If you add code like
this to the end of your modules, they’ll be ready for “doctesting”:
If you then run your module at your operating systems comand line, your
tests run. If all you want to do is import your module’s code and not run your
tests, the previous if statement supports doing just that.
For more on unittest and doctest, search the online Python
documentation on the Web or via IDLE’s Help menu.
438 appendix
leftovers
This code works fine, but it breaks when presented with the following value for
phone_number:
Why does this phone number
phone_number = "Cell (mobile): (555)-918-8271" cause the program to fail? Try
it and see what happens…
When you use a regular expression, you can specify exactly what it is you
are looking for and improve the robustness of your code:
This looks a little strange,
but this regular expression
is looking for an opening “(“
followed by three digits
and then a closing “)”.
This specification is much
more likely to find the
area code and won’t break
as quickly as the other
version of this program.
440 appendix
leftovers
442 appendix
leftovers
Threads do indeed exist in Python but should be avoided where possible. Don’t you like my
This has nothing to do with the quality of Python’s threading library and threads...?
everything to do with Python’s implementation, especially the implementation
known as CPython (which is more than likely the one you’re running
now). Python is implemented using a technology known as the Global
Interpreter Lock (GIL), which enforces a restriction that Python can only
ever run on a single interpreter process, even in the presence of multiple
processors.
What all this means to you is that your beautifully designed and implemented
program that uses threads will never run faster on multiple processors even
if they exist, because it can’t use them. Your threaded application will run
serially and, in many cases, run considerably slower than if you had developed
the same functionality without resorting to threads.
Main message: don’t use threads with Python until the GIL restriction is
removed…if it ever is.
444 appendix
leftovers
B
sending data to 300–303
tracking module for 248–249
troubleshooting 242, 247–250
“batteries included” 32 writing 236–238, 244–246
BIFs. See built-in functions writing for Android. See SL4A
BigTable technology 354, 359 cgi library 300
blue text in IDLE 4 cgitb module 248–249, 253
books chaining
Dive Into Python 3 (CreateSpace) 445 functions 146, 172
Head First HTML with CSS & XHTML (O’Reilly) methods 142, 172
374 chevron, triple (>>>) IDLE prompt 4
Head First Programming (O’Reilly) 443 chmod command 239, 253
Head First SQL (O’Reilly) 313
Learning Python (O’Reilly) 445 classes 189–191
Mastering Regular Expressions (O’Reilly) 440 attributes of 190, 194, 212
Programming in Python 3 (Addison-Wesley Profes- benefits of 189
sional) 445 converting data to dictionary 285–286
Python Essential Reference (Addison-Wesley Profes- defining 190–193, 194, 195–196
sional) 445 inherited 204–209, 212
Python for Unix and Linux System Administration instances of 190, 191, 194, 195–196
(O’Reilly) 445 metaclasses 439
braces. See curly braces methods of 190, 195–196, 198–200
brackets, regular. See parentheses in modules 209, 212
class keyword 191, 212
brackets, square. See square brackets
close() method, database connection 315, 350
BSD, running CGI scripts on 239
close() method, files 75, 103
build folder 42
code editors 35, 436. See also IDLE
built-in functions (BIFs). See also specific functions
displayed as purple text in IDLE 4 colon (:)
help on 21 in for loop 16
importing of, not needed 55 in function definition 29
namespace for 55 in if statement 20
number of, in Python 21 comma (,) separating list items 7
__builtins__ namespace 55, 71 comments 37–38
C
commit() method, database connection 315, 350
Common Gateway Interface scripts. See CGI scripts
cascading style sheet (CSS) 374–375 comprehension, list 154–159, 172, 409–411, 432
case sensitivity of identifiers 17 concatenation operator (+) 138
cgi-bin folder 234, 235 conditional list comprehension 409–411, 432
CGI (Common Gateway Interface) scripts 217, 235, 243, conditions. See if/else statement
253. See also WSGI connection, database
location of 234, 235 closing 314, 315
running 239 creating 314, 315
running from Android 264–265, 272–273, 283
448 Index
the index
connect() method, sqlite3 315, 350 rollback changes to data 314, 315, 350
context management protocol 120 schema for 317
SQLite for. See SQLite
Controller, in MVC pattern 221 tables in 317, 319–320, 350
for GAE webapps 359, 370–373
data folder 234
for webapps 234–238, 244–246
“copied” sorting 144, 145–146, 172 data interchange format. See JSON data interchange
format
CREATE TABLE statement, SQL 317, 319–320
data objects. See also specific data objects
CSS (cascading style sheet) 374–375 getting next item from 54
CSV format, converting to Python data types 401–405 ID for 54
curly braces ({}) length of, determining 32
creating dictionaries 180 names of. See identifiers
creating sets 166 datastore, for GAE 359–360, 380–383, 384–387, 395
cursor() method, database connection 315, 350 data types
custom code 131 converting CSV data into 401–405
converting strings to integers 54
custom exceptions 439
in datastore 360
D
immutable 91, 103, 116, 138
for JSON 285
for list items 8, 12
data
date and time data
for Android apps. See JSON data interchange format
format compatibility issues 418–423
bundling with code. See classes
property type for 362, 384–385
duplicates in, removing 161–163, 166–167
external. See database management system; files db.Blob() type 360
for GAE webapps. See datastore, for GAE db.DateProperty() type 360
nonuniform, cleaning 148–153 db.IntegerProperty() type 360
race conditions with 309–310
db.StringProperty() type 360, 385
Robustness Principle for 384–387
searching for closest match 416–417 db.TimeProperty() type 360
sending to web server 275, 291 db.UserProperty() type 360, 390
sorting 144–147, 172 decision statement. See if/else statement
storing. See persistence
transforming, list comprehensions for 154–159 decorators, function 439
database API 314–315, 350 def keyword 29, 191, 212
database management system 312 dialogCreateAlert() method, Android API 274, 276, 280
closing connection to 314, 315 dialogGetInput() method, Android API 295, 304–306
commit changes to data 314, 315, 350 dialogGetResponse() method, Android API 274, 276,
connecting to 314, 315 278, 280
cursor for, manipulating data with 314, 315
dialogGetSelectedItems() method, Android API 278, 280
designing 316–318
inserting data into 321, 324, 348 dialogSetItems() method, Android API 279, 280
integrating with Android apps 342–348 dialogSetNegativeButtonText() method, Android API
integrating with webapps 327–341 274, 276
managing and viewing data in 326 dialogSetPositiveButtonText() method, Android API 274,
process for interacting with 314–315 276, 280
querying 322, 332–333
dialogSetSingleChoiceItems() method, Android API 274, enable() function, cgitb 248, 253
276 end_form() function, yate 231, 233
dialogShow() method, Android API 276, 280 entities, in datastore 360, 395
dict() factory function 180, 212 enumerate() built-in function 54
dictionaries 178–182, 212 environ dictionary 300, 350
accessing items in 180, 212
equal sign (=)
compared to lists 179
converting class data to 285–286 assignment operator 7
creating 180, 182, 186 in function argument definition 63
dictionaries within 407–409 errors. See exception handling; troubleshooting
keys for 178, 180, 212 exception handling 88–95, 103. See also troubleshooting
populating 180, 212 benefits of 95, 100
reading CSV data into 403–404 closing files after 114–115, 120–123
values of 178, 180, 212 custom exceptions 439
dir() command 225 defining with try/except statement 89, 91–94
directory structure. See folder structure ignoring found errors 93–94
IndexError exception 17
dist folder 42
IOError exception 103, 112–114, 117–119
distribution for missing files 96–98
creating 40–42 NameError exception 44, 118
updating 60–61, 65 PickleError exception 133–134
uploading to PyPI 48 specific errors, checking for 101–102
Dive Into Python 3 (CreateSpace) 445 specific errors, details about 117–119
djangoforms.ModelForm class 368 TypeError exception 56–57, 116, 247–249, 283–285
ValueError exception 78–79, 81–82, 103
Django Project
exception objects 119, 138
Form Validation Framework 368–369, 395
templates 363–366, 395 except keyword. See try/except statement
doctest framework 438 execute() method, cursor 315, 322, 324, 350
documentation for Python 3 3, 80, 103 extend() method, lists 10
dot notation 10, 194, 196
double quotes. See quotes F
dump() function, pickle 133–134, 138 F5 key, IDLE 39, 44, 49, 71
dumps() function, json 269, 272, 281, 291 factory functions 166
dynamic content 216, 217 favicon.ico file, for webapp 234
E
fetchall() method, cursor 322
fetchmany() method, cursor 322
Eclipse editor 35 fetchone() method, cursor 322
editors 35, 436. See also IDLE FieldStorage() method, cgi 244, 253, 296, 300, 350
elif keyword 108. See also if/else statement files. See also persistence
else keyword. See if/else statement access modes for 110, 133
appending data to 110
emacs editor 35, 436 checking for existence of 118
450 Index
the index
H
from statement 46, 49
functional programming concepts 157
function decorators 439 hashes. See dictionaries
functions header() function, yate 231, 233
adding arguments to 52, 66–68 Head First HTML with CSS & XHTML (O’Reilly) 374
anonymous 439
built-in. See built-in functions (BIFs) Head First Programming (O’Reilly) 443
I
from keyboard after screen prompt 413–414, 432
input() built-in function 413–414, 432
id() built-in function 54 insert() method, lists 10, 14
IDE 436. See also IDLE INSERT statement, SQL 321, 324, 348
identifiers 7, 17, 32 instances of classes 190, 191, 194, 195–196
IDLE 3–5, 32 int() built-in function 54
colored syntax used in 4 integers, converting strings to 54
indenting enforced in 4 interface. See View, in MVC pattern
preferences, setting 5
IOError exception 103, 112–114, 117–119
prompt in (>>>) 4
recalling and editing code statements 5 I/O (input/output), handling. See files
running or loading code in 39, 44, 49, 71 isinstance() built-in function 20–22, 32
TAB completion 5 iterations
if/else statement 20, 32 for loop 15–17, 19–22, 32
elif keyword 108 generating with range() function 54–56
in list comprehension 432 while loop 16–17
negating conditions in 86, 103
images folder 234
immutable data types 138
J
lists 91, 103, 116 JSON data interchange format 266–267, 291
numbers 116 API for, using 269–272
strings 116 browser differences with 272
data types supported by 285
import statement 43, 46, 49, 71
incompatibility with pickle data objects 284–285
include_footer() function, yate 230, 232, 233
include_header() function, yate 230, 232
indentation rules
K
enforced in IDLE 4 KDevelop IDE 436
for for loops 16 keys, in dictionary 178, 180, 212
for function definitions 29 keywords, displayed as orange text in IDLE 4
for if statement 20
452 Index
the index
L
locals() built-in function 118, 138
loops. See iterations
M
lambda expression 439
Learning Python (O’Reilly) 445
len() built-in function 10, 32 Mac OS X
lib folder 42 code editors for 35
Linux GAE log messages on 378
IDEs for 436
code editors for 35
installing Python 3 on 3
GAE log messages on 378
running CGI scripts on 239, 272
IDEs for 436
running GAE Launcher on 357
installing Python 3 on 3
transferring files to Android device 288
running CGI scripts on 239, 272
running GAE Launcher on 357 __main__ namespace 45
transferring files to Android device 288 MANIFEST file 42
list() built-in function 54 mappings. See dictionaries
list comprehension 154–159, 172, 409–411, 432 Mastering Regular Expressions (O’Reilly) 440
lists 32. See also data objects metaclasses 439
adding items to 10–14 methods 190. See also specific methods
bounds checking for 17 chaining 142, 172
classes inherited from 204–208 for classes 195–196, 198–200
compared to dictionaries 179 creating 212
compared to sets 167 results of, as attributes 250, 253
creating 6–7, 54 self argument of 212
data types in 8, 12
duplicates in, removing 161–163 ModelForm class, djangoforms 368
extracting specific item from 175–176 Model, in MVC pattern 221
getting next item from 54 for GAE webapps 359, 360–362
identifiers for 7 for webapps 222–225
immutable 91, 103, 116 Model-View-Controller pattern. See MVC pattern
iterating 15–17, 157
modules 34–36, 71
length of, determining 10, 32
adding functionality to 50–52
methods for 10
classes in 209, 212
nested, checking for 20–22
creating 35
nested, creating 18–19
distribution for, creating 40–42
nested, handling 23–25, 28–31
distribution for, updating 60–61, 65
numbered, creating 54
distribution for, uploading to PyPI 48
reading CSV data into 403–404
importing 43–44, 46
removing items from 10
loading in IDLE 39, 49, 71
similarity to arrays 9–10, 17
locations for 38, 49
slice of 160, 172
namespaces for 45–46, 71
load() function, pickle 133, 138 in Python Standard Library 36
loads() function, json 269, 276, 280, 291 third-party 36
O
.pyc file extension 42, 49
.py file extension 35
object relational mapper. See ORM (object relational PyPI (Python Package Index) 36
mapper) registering on website 47
objects. See data objects uploading distributions to 48
uploading modules to 209
open() built-in function 75, 103, 109–110
Python 2
orange text in IDLE 4 compared to Python 3 17
ORM (object relational mapper) 442 raw_input() built-in function 432
os module 76, 300, 350 running on Android smartphones 258–259, 291
using with Google App Engine 355
P Python 3
compared to Python 2 17
para() function, yate 231, 233 documentation for 3, 80, 103
parentheses ((...)) editors for 35, 436
enclosing function arguments 29 installing 3
enclosing immutable lists 91 interpreter for. See IDLE
learning 445
pass statement 93, 103
python3 command
persistence 105 building a distribution 41
pickle library for 132–137 checking for Python version 3
reading data from files 222–224 installing a distribution 41
writing data to files 110–113, 222–224 uploading a new distribution 68
PickleError exception 133–134
454 Index
the index
T
sqlite3 command 326
sqlite3 library 313, 315, 350
SQLite Manager, for Firefox 326 TAB character, printing 56
SQL (Structured Query Language) 313, 350. See TAB completion, IDLE 5
also NoSQL; SQLite; ORM
tables, database 317, 319–320, 350
square brackets ([...])
target identifiers, from split() method 77, 91
accessing dictionary items 180, 212
accessing specific list items 9, 18 .tar.gz file extension 42
enclosing all list items 7, 18 Template class 230, 253
standard error (sys.stderr) 248, 291 template module 364
standard input (sys.stdin) 291 templates folder 234, 370
Standard Library, Python 36 templates for GAE 363–366, 395
standard output (sys.stdout) 126–128, 291 testing code 438
start_form() function, yate 231, 233 TextMate editor 35, 436
start_response() function, yate 230, 232 third-party modules 36
static content 216, 217 threads 444
static folder 370 time data
str() built-in function 119, 138 format compatibility issues 418–423
strings property type for 362, 384–385
concatenating 138 time module 419, 432
converting other objects to 119 Tk Interface (tkinter) 443
converting to integers 54 traceback 88, 103. See also exception handling; trouble-
displayed as green text in IDLE 4 shooting
finding substrings in 84–86
tracing code 58–59
immutable 116
sorting 148 triple chevron (>>>) IDLE prompt 4
splitting 77–78, 80–81 triple quotes (“””...””” or ‘’’...’’’) enclosing comments 37
substitution templates for 230, 253 troubleshooting. See also exception handling
strip() method, strings 108, 138, 142 404 Error, from web server 242
Structured Query Language. See SQL 405 Error, from web server 378
stylesheets for HTML forms 374–375 Android apps 277
GAE webapps 378
suite 16, 29, 32
testing code 438
sys module 291 tracing code 58–59
sys.stdout file 126–128, 138 try/except statement 89, 93–94, 101–102, 103
finally keyword for 115, 138
with statement and 120–123
tuples 91, 103, 116
TypeError exception 56–57, 116, 247–249, 283–285
456 Index
the index
U
webapps 215–217, 253
controlling code for 221, 234–238, 244–246
data modeling for 221, 222–225
u_list() function, yate 231, 233 designing with MVC 221
unittest module 438 design requirements for 218–220
urlencode() function, urllib 291 directory structure for 234
Google App Engine for. See GAE
urllib2 module 291
input data, sending to CGI scripts 300–303
urllib module 291 input forms for. See forms, HTML
urlopen() function, urllib2 291 SQLite used with 327–341
user authorization 389–393 view for 221, 226–233
Web-based applications. See webapps
user input. See forms, HTML; input
web frameworks 441. See also CGI; WSGI
UserProperty() type, db 390
web request 216, 253, 395
W Y
“w” access mode 110 yate (Yet Another Template Engine) library 226–233
“w+” access mode 110 yield expression 439
“wb” access mode 133