Programming Python

Errata for Programming Python

Submit your own errata for this product.

The errata list is a list of errors and their corrections that were found after the product was released. If the error was corrected in a later version or reprint the date of the correction will be displayed in the column titled "Date Corrected".

The following errata were submitted by our customers and approved as valid errors by the author or editor.

Color key: Serious technical mistake Minor technical mistake Language or formatting error Typo Question Note Update

Version Location Description Submitted By Date submitted Date corrected
PDF, Other Digital Version
Page xxviii
at top of page in continuation of previous paragraph

"Only a fraction of Python users must care about linking in C libraries
today, and those who do already have the skills required to read the larger and
more ***compete*** example of integration present in the source code of Python itself."

Type at ***, suppose to be complete? It's in pdf and epub that I just got.

Note from the Author or Editor:
Yes - a typo. Please fix in reprints.

Anonymous  Dec 16, 2010  May 13, 2011
, Printed, PDF, Safari Books Online, Other Digital Version
Page xxviii
line 3 from page top

[Feb-1-11] Page xxviii, line 3 from page top: two typos in same sentence

This text's "larger and more compete example" should be "larger and more complete examples".

Mark Lutz
Feb 02, 2011  May 13, 2011
Printed, PDF, ePub, Mobi, Safari Books Online, Other Digital Version
Page N/A

There is a new patch for running PyMailGUI under Python 3.3, which fixes a display issue for non-ASCII email address names introduced by an incompatible change in Python's email package. For details and the simple patch, please see:

Mark Lutz
Sep 15, 2013 
Printed, PDF, ePub, Mobi, Safari Books Online
Page N/A

[Oct 2013, No Edits Required, informational only]

There's a new release of the book's examples package, updated for Python 3.3. Examples in this 1.4 release work with all 3.X Pythons 3.3 and earlier (and possibly later). It's available at the book's official examples site:

For details on the minor changes incorporated, as well as 3.3 usage pointers, see the package's new README file at either the preceding site or here:

Note that all major examples tested run correctly on Python 3.3 (and 3.4), but some minor examples will reflect changed behavior in Python after 3.1. Most notably, the email package and CGI scripts mutated, though larger email and web examples still work as shown in the book with the patches listed above.

If you find a deviation in book example behavior under later 3.X releases, consider it a valuable lesson; change is a fact-of-life in software in general, and open source in particular. Some such change is even beyond Python's scope; the recent trend towards using odd Unicode characters in emails, for instance, has potential to break many an email client, the book's clients included. Feel free to change as the future merits.

Mark Lutz
Oct 16, 2013 
Page N/A line 274

For the score algorithm:

def scoreMove(self, T1, T2):
(row, col) = T1
((countRows, countCols), (countDiag1, countDiag2)) = T2
return (
countCols.get((col, self.machineMark), 0) * 11 +
countRows.get((row, self.machineMark), 0) * 11 +
countDiag1[self.machineMark] * 11 +
countDiag1[self.machineMark] * 11
countCols.get((col, self.userMark), 0) * 10 +
countRows.get((row, self.userMark), 0) * 10 +
countDiag1[self.userMark] * 10 +
countDiag1[self.userMark] * 10
countCols.get((col, Empty), 0) * 11 +
countRows.get((row, Empty), 0) * 11 +
countDiag1[Empty] * 11 +
countDiag1[Empty] * 11)

It seems you counted the same diagonal (countDiag1) twice.

Shouldn't you use countDiag2 as well?

countDiag1[self.machineMark] * 11 +
countDiag2[self.machineMark] * 11

countDiag1[self.userMark] * 10 +
countDiag2[self.userMark] * 10

countDiag1[Empty] * 11 +
countDiag2[Empty] * 11)

Am I missing or assuming something wrong here???

Note from the Author or Editor:

Yes, column 2 should probably be counted per your post. But this code is in an external supplemental example, is not shown in the book, and is not used by the book's briefly-presented PyToe example, which selects moves with the much better Minimax search alternative in the referenced file. Hence, no update required, but the following provides some historical context for interested readers.

This code is part of the PyToe tic-tac-toe game program. It appears in just 1 of the 5 move selection alternatives in the external example file (not counting variants never ported to 3.X form). It uses a very simplistic scoring heuristic, which was later subsumed by alternatives that proved better (and not just because of this typo).

Frankly, I haven't seen this code since it was written for the 2nd Ed in 1999; it wasn't used after initial development, and has largely existed as cruft since then. Even the 2nd Ed did not list any PyToe source code, because it requires some AI background that was deemed beyond the book's scope,

Since then, the 3rd and 4th Eds have continued to relegate PyToe to an external-only example, mentioned only as supplemental reading, and presented with just a screen shot and blurb to pique interest. Like the Holmes expert system similarly mentioned in the book, AI gaming is fun stuff, but out of scope in general.

In other words, great catch: I'm amazed that this obscure code received scrutiny after 16 years.

Michael Basca  Feb 19, 2015 
Safari Books Online
"Using Book Examples" section

Yes, I see the same link repeated twice.

Note from the Author or Editor:
Thanks for the update; this must be a Safari-only URL auto-adjust, then. I'm sure this change was well intended and hope it doesn't prove to be a can of worms, but we'll investigate.

Alexander  May 27, 2020  Feb 26, 2021
Page N/A
"" Line 87

For the following lines of code in

parent.__colpos + Colsz*.25, # from x-y, to x-y
parent.__rowpos + Rowsz*.5,
colpos + Colsz*.25, rowpos, arrow='last', width=1)
node.__rowpos = rowpos
node.__colpos = colpos # mark node, private attrs

You noted that __rowpos and node__pos are private attrs (name mangled).

But is that really the case? Aren't you just assigning an attribute to an object that just so happens to have a double underscore and assigning a value to it rather than defining it in the class definition. For example:

Class A:
__x = 1

AttributeError: type object 'A' has no attribute '__x'


Class A:

A.__x = 1


Doesn't the fact that accessing the attributes in the following code:

parent.__colpos + Colsz*.25, # from x-y, to x-y
parent.__rowpos + Rowsz*.5,

imply that they they aren't private?

I could have just easily removed the underscores and the code would work just the same:

parent.colpos + Colsz*.25, # from x-y, to x-y
parent.rowpos + Rowsz*.5,
colpos + Colsz*.25, rowpos, arrow='last', width=1)
node.rowpos = rowpos
node.colpos = colpos

Note from the Author or Editor:

No; sorry, but this post seems to reflect a lack of understanding of Python's pseudo-private attributes mechanism, an oddly obscure but very useful tool. Please see Learning Python for language fundamentals like this; as stated in the Preface, they are prerequisite to the applications-programming topics in Programming Python. One can't really jump into applications successfully without understanding language basics. In any event, this post refers to code included as a supplemental example but not listed or covered in the book itself, and the code referenced is not in error.

As a brief review, though, pseudo-private names are coded with double underscore prefixes, and are expanded by Python at each of their appearances anywhere in the class. In this case, the double underscore names are used intentionally for administrative data added by the tree sketcher, so that they won't clash with attribute names used by the application whose trees are being sketched. Because each of the class's "__X" names is stamped with the class name automatically, they are different from the same name used in other classes. In simpler terms:

>>> class TreeViewer:
... def __init__(self):
... self.rowpos = 88 # non-mangled: application's data
... self.__rowpos = 99 # mangled, pseudo-private: tool's data
... def update(self):
... self.__rowpos += 1 # update mangled tool name (only!)
>>> tv = TreeViewer()
>>> tv.__dict__
{'rowpos': 88, '_TreeViewer__rowpos': 99}

>>> tv.update()
>>> tv.__dict__
{'rowpos': 88, '_TreeViewer__rowpos': 100}

>>> tv.rowpos
>>> tv._TreeViewer__rowpos

By mangling the names to include the enclosing class's name, Python more or less guarantees that they are unique, distinct from the attributes of any other class in an application, and not accessible from outside the class without using their fully-mangled form (in this case, obj._TreeViewer__rowpos) .

It's not full privacy (see the decorators chapter in Learning Python 5E for an approach to implementing that with attribute tools), but suffices to minimize name collisions. If the code had just used "node.rowpos" without the underscores, it would possibly overwrite an attribute of the same name being used by the subject application.

Michael Basca  May 14, 2015 
Printed, PDF, ePub, Mobi, Safari Books Online
Page N/A
General note

[No Edits Required, Supplemental Page Note]

There is a supplemental list of items posted to the errata page by reader Michael Basca in the first half of 2015, at this location:

This reader posted 48 notes in all -- a new record for my books. Of these, I've retained 16 posts in the errata list here that either warranted book patches, or were deemed to be of potentially broad interest. The remaining 31 posts (after removing 1 duplicate) were moved to the supplemental page, which includes:

1) Notes on changes in recent Python releases impacting book examples.
2) Usage pointers for Mac/Linux users, regarding tkinter and web servers.
3) A few untested patches for Python library changes (e.g., nntplib, PyCrypto, urllib.request).

We can't update this book for changes in Python after its publication, of course, and such issues would naturally be isolated and addressed in a next edition, if one ever appears. The goal of the supplement posts page is to provide a resources for readers who may be stumbling onto the same issues.

Note to reprints: none of the items on the supplemental page require book edits.

Mark Lutz
Aug 02, 2015 
Reading on Kindle, so don't know, but look for text

C:\...\ PP4E\ System\ Streams > type adder2. py
import sys
sum = 0
while True:
line = sys.stdin.readline()
if not line: break
sum + = int( line)
print( sum)

IF THE LINE IS EMPTY, IT SEEMS TO READ '\n' so that 'not line' is still true. What am I missing?

Also, another form is:

import sys
sum = 0
line = '0'
while line != '\n':
sum += int(line):
line = sys.stdin.readline()
print( sum)

I have trouble with the 'while True' construct, so I try to avoid it.

Note from the Author or Editor:
[No changes required; information reply only]

This example assumes there are no empty lines in the file -- only lines having numbers -- and hence doesn't allow for a bare '\n' (though you're welcome to expand the 'if' statement to do so). This code also relies on the fact that file.readline(), unlike input(), returns the empty string at end-of-file, not a '\n' or an exception (this may or may not be part of the confusion):

~$ py3
>>> fname = '/Users/blue/Documents/temp.txt'
>>> open(fname, 'w').write('line1\nline2\n')
>>> print(open(fname).read())

>>> fobj = open(fname)
>>> fobj.readline()
>>> fobj.readline()
>>> fobj.readline()

WRT the alternate coding: you're version works well too, of course, and may be preferred by some structured-programming purists, but the 'while True:' idiom is pervasive in Python code. Personally, I'd probably code a simple loop like this using either the iterator- or generator-based versions shown in the book next, and reserve explicit loops for cases where there was more to the processing logic than a summation:

for line in sys.stdin: sum += int(line) # file line iterator
sum(int(line) for line in sys.stdin) # generator form

Clifford Ireland  Oct 20, 2015 
Printed, PDF, ePub, Mobi, Safari Books Online
Page various

The book support site mentioned on various pages has moved from:
to its new home at:

The prior location will retain forwarding pages if you wind up there due to old printings or links, but please adjust any references to the old site.

EDIT: For reader convenience, let's change this in reprints. Specifically, run a global replacement of both of the following:
to the site's new URL:
This includes appearances in the book's text; in any code listings (if there is adequate line space for the change), and in any hyperlinks.

Mark Lutz
Nov 14, 2015 
Safari Books Online
"Using Book Examples" section

The two given links to the author's website are equal:
1. "As before, examples, updates, corrections, and supplements for this book will be maintained at the author’s website, which lives officially at the following URL:"

2. "This page at my book support website will contain links to all supplemental information related to this version of the book. Because I don’t own that domain name, though, if that link ceases to be during this book’s shelf life, try the following alternative site as a fallback option: (alternative location)"

I excluded 'http' as URLs aren't allowed to be present in the description of the error.

Note from the Author or Editor:
I'm not sure what the poster is describing here; is the same link text repeated twice in Safari? That's not what I'm seeing in the current book PDF or print, which lists two different URLs in the Preface - an original default and a newer alternative: (alternative location)

As of today, the first of these forwards to the second, and the second is automatically redirected to the current page at the following URL (via Apache rewrite-rules magic):

Hence, the Preface's links both resolve to the same URL today, but the Preface's referenced text reflects an expected future which has now happened, and I don't see any reason to rewrite this history today. That said, it's marginally better to be current on the second link, so let's change the first of the following to the second in the Preface: (alternative location) (alternative location)

BUT: if Safari indeed lists the same text for both links here, please let me know. It's possible that this has been changed automatically in Safari, but that would invalidate the Preface's narrative, and be an incorrect glitch from my perspective -- and be cause for concern over how many other links Safari may have munged wrongly...

Alexander  Apr 12, 2020  Feb 26, 2021
Printed, PDF, Safari Books Online, Other Digital Version
Page 12
5th Python prompt

Python prompt has two angled-brackeds, instead of three:
>> bob2['job'][-1]
should read:
>>> bob2['job'][-1]

Note from the Author or Editor:
Yes -- add an extra ">" to the ">>" prompt here (it should be ">>>").

Daniel Daz  Mar 24, 2011  Nov 11, 2011
Page 21
Example 1-7 heading

The Example 1-7 heading lists the filename as when it should be

Note from the Author or Editor:
Yes -- fix as described (use underscores instead of dashes to match the file's name in later interaction).

Brad Trotter  May 24, 2011  Nov 11, 2011
Page 38/39
example 1-22

example 1-22 Neither my typed code nor the cut and paste code from the online example code at O'reilly produce the result printed in the paperback edition. This code contains both formatting errors and logic errors.

Note from the Author or Editor:
This reflects a bug in Python 3.2.0, not in the book's examples. Please see:

for details. In short, Python's input() built-in is broken in 3.2.0 (3.2) when used in Windows console mode only. This built-in has been fixed in later Python releases. The quickest fix is to upgrade to 3.2.1 or later, or try a different environment; examples in the book which use input() work fine in all other Python versions and in most other contexts such as IDLE.

Charles Wehrenberg  Dec 14, 2011 
Page 61

I had to put

#!/usr/bin/env python

at the top of the code
Example 1-34. PP4E\Preview\cgi-bin\

Otherwise, the browser will try to download the script rather than executing it.

Not sure why, but my environment is Python 3.4.1 on mac os 10.10.5, using firefox browser,
and using the script from the book, but I customized
Example 1-32. PP4E\Preview\

a little bit:

instead of:
srvraddr = ("" port)

I used
srvraddr = ("python1", port)

Because that's how I've setup my host on my machine.

Note from the Author or Editor:
Looks like we have space for a clarification, so: change the start of page 55's last paragraph from the first of the following to the second:
"We’ll revisit the tools used in this example later in this book."
"We’ll revisit the tools used in this example later in this book (and explore Unix CGI script configuration requirements we’ll skip here)."

[Discussion only follows]

Not sure why your setup required the hostname change you described, but this example and others like it work for me on a Mac today if I perform the following additional configuration steps that are not required on the book's Windows test platform, but are all covered later in the book:

1) Change the server script's port number to 8888 from the precoded 80, and use "localhost:8888" in all URLs, as described in this chapter. (A "su" might enable running a server on port 80, but I didn't care to try.)

2) Add the initial "#!/usr/bin/env python3" (or "#!/usr/bin/python for 2.X) as you suggested.

3) Convert the CGI script file to UNIX end-line format (from DOS format), using a simple utility script I have (there are many other ways to do this: see the web).

4) Give the CGI script executable permission with a "chmod a+x".

These steps are all spelled out in detail in Chapter 15, where CGI scripting is covered in full. As stated up-front, the chapter hosting the example in question is just a quick preview that's deliberately short on such details for space.

As a broad rule, this book's web scripting code does work as shown and described (running locally, on Windows, in Python 3.1, etc.). Beyond that, though, both Python's evolution and CGI and server details are too variable to guarantee universal portability. That said, the examples do work on the latest Pythons and platforms, and I'm glad that some readers are able to work out the kinks on their computers as needed.

lingtalfi  Aug 25, 2015  Jan 06, 2017
Page 61

In order to make "Example 1-34. PP4E\Preview\cgi-bin\" work correctly, I had to change the following:

def htmlize(adict):
new[field] = cgi.escape(strvalue))

instead of:

new[field] = cgi.escape(repr(value))

And in

def updateRecord(db, form):
setattr(record, field, form[field].value)

instead of:
setattr(record, field, eval(form[field].value))

Otherwise, the would crash and I would have a blank screen in the browser

Note from the Author or Editor:
Looks we have space for a clarification, so: on page 65, expand the start of the last paragraph from the first of the following to the second, with literal font on its "eval":
"To update a record, fetch it by key, enter new values in the field inputs,"
"To update a record, fetch it by key, enter new values in the field inputs (and remember to quote string input values here just as in the earlier versions for the script's eval),"

[Discussion only follows]

No - you don't need to make the changes you suggested. You only need to remember to quote string values in the form's fields as in code (enter 'spam' instead of spam), and the example works as shown and described in the latest Pythons, and with no code changes.

You probably missed the discussion of eval() in the console and GUI versions of this example which precede it. Though it's not repeated here a third time, the earlier variations make it fairly clear that the form field's values (except for the key) are assumed to be in Python code syntax, and are run through eval() to convert them to Python objects. This is deliberate so that digits are converted to numbers, but means you must quote string values. Here's one of the excerpts from the chapter (see also the code's admittedly-terse comment "# eval: strings must be quoted"):

Notice how we’re using repr again to display field values fetched from the shelve and eval to convert field values to Python objects before they are stored in the shelve. As mentioned previously, this is potentially dangerous if someone sneaks some malicious code into our shelve, but we’ll finesse such concerns for now.

Keep in mind, though, that this scheme means that strings must be quoted in input fields other than the key—they are assumed to be Python code. In fact, you could type an arbitrary Python expression in an input field to specify a value for an update. Typing "Tom"*3 in the name field, for instance, would set the name to TomTomTom after an update (for better or worse!); fetch to see the result.

With your suggested edits, every field value will be a text string object. That works (and you won't have to quote string values in the form), but is limiting, and was not the intent of this chapter's running example. Really, fields should probably have associated type information, but that's too much infrastructure for this simple preview-chapter example.

All that being said I'm adding a parenthetical clarification here for readers who may similarly have skipped to the web version. For the record, I've just verified that this example works under Python 3.5 on both Windows and Mac OS X when strings are quoted as required, though you must also make the unrelated configuration changes on Mac OS X and Linux outlined in the reply to your other post here.

lingtalfi  Aug 25, 2015  Jan 06, 2017
Page 63, 67
markup code

Because of the evolving html standard, as of the publication for the fourth edition of this book, the html table elements (<tr>, <th>, <td>) should specify closing code (</tr>, </th>, </td>.

Note from the Author or Editor:
[No change required] The book states clearly that it omits some HTML tags on purpose for simplicity and space. I agree with this poster's point in principle, but it doesn't merit changes in the book given its explicit and broad statements on this point.  Dec 01, 2011 
PDF, ePub
Page 79
First paragraph under title "A Custom Paging Script", line 4

Line 4 of this paragraph read:

"relying on manual use if the scrollbar ..."

in this sentence, "should" be "of", that is, this sentence should read:

"relying on manual use of the scrollbar"

Note from the Author or Editor:
Yes - valid typo. Please change as noted, changing the "if" to "of".

wangshuaijie  Jan 16, 2016  Jan 06, 2017
Printed, PDF, ePub, Mobi
Page 121
line -7 of example 3-8

EDIT: Change the referenced code line from the first of the following to the second (only adding 'y' and 'Y' in the bracketed ilst, and using straight quotes):

if lines and getreply() not in [b'y', b'Y']:
if lines and getreply() not in [b'y', b'Y', 'y', 'Y']: break

DISCUSSION: A reader named Zhaoliang reported this on My reply follows:

This looks like a minor but valid bug to me. To support all its target use cases, the code line in question (line -7 of example 3-8 on Page 121) should indeed be changed from the first of the following to the second (adding 'y' and 'Y'):

if lines and getreply() not in [b'y', b'Y']:
if lines and getreply() not in [b'y', b'Y', 'y', 'Y']: break

Otherwise, the code:

1) *Works* for NON-TTY cases, regardless of user input. In these cases, stdin is a file or pipe, and msvcrt.getche() is used for user input. Command lines take this form:

python < inputfile
type inputfile | python

2) *Fails* for TTY cases, but only if user input is 'y' or 'Y'. In these cases, stdin is a console, and the builtin input() is used for user input. Command lines take this form:

python inputfile

The reason #2 fails is that input() returns a str string, but msvcrt.getche() returns a bytes; to allow for _either_, we need to include both bytes and str in the response list. For simplicity, you might also code the line this way:

if lines and getreply().lower() not in [b'y', 'y']: break

I don't recall the history on this example, but it was likely either not retested in all usage contexts (perhaps 'y' was not verified); assumed a pending 3.X library change that never appeared (return types probably should be consistent); or was inadvertently tested under Python 2.X (though that's unlikely, given its 3.X input() call).

Thanks to the reader for the notification; I will post this as an errata for the book, to be repaired in the next reprint, which is coming up early next month.

Mark Lutz
Jul 31, 2015  Aug 07, 2015
Page 152
2nd example

bytes =
values = struct.unpack('>>i4shf', data)

NameError: name 'data' is not defined

Should read as
data =
values = struct.unpack('>>i4shf', data)

Note from the Author or Editor:
Yes -- change as described: page 152, 2nd code listing, line 4 should read as follows (change "bytes" at the end to "data"):

values = struct.unpack('>>i4shf', data)

The code actually works as is if you keep typing all the code on this page in sequence (as I must have done), because "data" is what we called the byte string when it was written to the file; that is, "data" from the first interaction is the same as the "bytes" read off the file in the second interaction. This wasn't the intent, though.

Bob Sanford  May 24, 2011  Nov 11, 2011
Page 152
first example; second line

Running Python 3.2.2 on Win 7 machine

data = struct.pack('>i4shf', 2, 'spam', 3, 1.234)
data = struct.pack('>i4shf', 2, b'spam', 3, 1.234)

this correction is suggested by the values = return in the second example.

Note from the Author or Editor:
This reflects a recent change in Python 3.2, not a problem with the book examples. In short, 3.2 has removed existing struct.pack functionality for str strings and the "s" type code, and now allows only bytes strings in this context. In 3.2 and later, you must manually encode Unicode str to bytes prior to this tool, using str.encode() or bytes().

This change also impacts some examples in Learning Python, and one example in Python Pocket Reference. For more details, including the fix, please see this note on my book update pages (or its cross-post on Programming Python's updates page):

I can't post here about every change in Python that will impact the books over time, but this change seemed to merit a few words.

Charles Wehrenberg  Jan 07, 2012 
Page 216
last 10th line, a Popen object’s returnvalue attribute

A variety of interfaces in the subprocess module (e.g., the call function’s return
value, a Popen object’s returnvalue attribute and wait method result)

a Popen object has a returncode attribute, not returnvalue

Note from the Author or Editor:
Yes - please change as noted, from the first of the following to the second:
a Popen object’s returnvalue attribute and wait method result)
a Popen object’s returncode attribute and wait method result)

(In the book's defense, it uses returncode correctly in 5 other places, including 1 other sentence and 1 code example in this section; 2 earlier code examples Chapter 3; and 1 more example in Chapter 2. Hopefully, most readers got past a sole exception.)

gnoweb  Aug 09, 2015  Jan 06, 2017
Printed, PDF, ePub
Page 322
code block 2

"C:\...\PP4E\Tools> python *.py .. | more"
It seems I have to quote "*.py" for this command to work in Linux. Didn't try Windows though.

Note from the Author or Editor:
Changing this to a clarification, not an errata. To address, change the very end of paragraph 2 on Page 322 from:
"...standard input stream:"
"...standard input stream (remember to quote the "*.py" on Unix and Linux shells only, to avoid premature pattern expansion):"

The report is correct about needing to quote some command-line arguments on Linux (only), and this does merit a note here. This finder directory tree search script matches the passed-in "*.py" pattern itself, so we don't want a Linux shell to do expansion before it's passed in.

However, shell differences are mentioned elsewhere; this book runs all its examples on Windows, where this isn't an issue; and the book cannot note shell differences at each command where syntax diverges without becoming a shell programming guide too.

On this specific point, the following text appears on Page 106 (before the command line in question) in an early section devoted to command lines in general:
(portability note: you may need to add quotes around the *.py in this and other command-line examples to prevent it from being expanded in some Unix shells):
Moreover, Unix shell globbing behavior is also described on the earlier Page 166, and the closer Page 320-321.

Both linear readers of the book and Linux users in general should probably be aware of the issue well enough to argue against redundant warnings throughout an already large text (and quoting arguments in general for Linux readers would likely confuse others with less command-line experience to draw from).

Nevertheless it's worth adding the text in the book and retaining this note as a reminder for Linux readers here -- thanks for the report.

Yang Lifu  Sep 09, 2013  Jan 17, 2014
Page 323
1st paragraph/sentence

Last sentence of 322 going into 323:

"Here’s a more complex example of our find module at work: the following system command line lists all Python files in directory C:\temp\PP3E whose names begin with the letter q or t. Note how find returns full directory paths that begin with the start
directory specification:"

Requested change:

"q or t" --> "q or x"

As per example code.

Note from the Author or Editor:
Yes, change the referenced text to "q or x". (Most likely a last-moment change, but very minor, and likely obvious given the multiple surrounding examples that search for q or x, as noted by the poster.)

Michael Basca  Dec 26, 2014  Aug 07, 2015
Page 325
2nd Paragraph or 2nd code block

Original Command

"C:\...\Examples\PP4E> Tools\"

Should be:

"C:\...\Examples\PP4E> Tools\ .."

Since the output shows the the .pyc files being removed in the parent directory tree.

Note from the Author or Editor:
[NO EDITS REQUIRED, informational only, changed to clarification]

Not true. A ".." wouldn't hurt, but is not required here. When running the script from one level up in the shell, the current working directory (CWD) used by default is the directory the shell is working in, not the script's own directory. Using Python 3.3, in a Windows shell (Command Prompt):

C:\...\PP4E> Tools\
=> C:\...\PP4E\Ai\TicTacToe\__pycache__\tictactoe_lists.cpython-33.pyc
=> C:\...\PP4E\Dbase\__pycache__\testdata.cpython-33.pyc
=> C:\...\PP4E\Dbase\__pycache__\__init__.cpython-33.pyc
=> C:\...\PP4E\Dstruct\Classics\__pycache__\btree.cpython-33.pyc

This is why the immediately-following example in the book is run one level down, in the scripts own directory, to illustrate the difference:

C:\...\PP4E\Tools> .
=> .\find.pyc
=> .\visitor.pyc
=> .\__init__.pyc
Found 3 files, removed 3

For a refresher on this, see "CWD, Files, and Import Paths" on page 104.

Michael Basca  Dec 26, 2014 
Page 329
Note at the end of "A Python Tree Searcher"

The second paragraph discussing the textexts list used in the program states:
"Also notice the textexts list in Example 6-17, which attempts to list all
possible binary file types ..."

The list actually contains text file types (.py, .pyw, .txt, etc), not binary file types.

Note from the Author or Editor:
Yes. I believe the example number may have been off here (there's a skipexts binary file types list ahead in Example 6-18), but it's simpler to adjust the text as it is. At the referenced note location, change the start of line 2 from the first of these to the second:

"possible binary file types:"
"possible text file types:"

Jason  Oct 01, 2014  Aug 07, 2015
Page 344
3rd paragraph

Original line of code:

def trace(*args): print(*args) # with spaces between

Should be:

def trace(*args): print(args) # with spaces between

The asterisk should be removed in the print statement.

Note from the Author or Editor:
[NO EDITS REQUIRED, retained as informational, changed to clarification]

No, the star is required. This is a common pattern for general trace routines; the first star in the header means "collect any number of positional arguments in a tuple", and the star in the print() call means "unpack the arguments tuple into separate arguments". The combined effect allows any number of arguments, and passes them on to print() so that they display individually, with spaces between them, and without enclosing tuple parenthesis:

>>> def trace(*args): print(*args) # with spaces between
>>> trace('spam', 99, 3.1415)
spam 99 3.1415
>>> trace('spam')

See Learning Python for more background on such language fundamentals.

Michael Basca  Dec 27, 2014 
Printed, PDF, ePub
Page 442, 564, etc.
win.grab_set() paragraph, grids, etc.

[Last changed August 2015, No Edits Required, informational only]


1) Dialogs and wait_visibility() on Linux (page 442)

On Linux, you probably need to call win.wait_visibility() before win.grab_set() when creating a modal dialog this way. This platform disallows grabs until a window is visible; without the added call, the grab fails with an exception and the dialog isn't modal.

This book's examples were run and tested on Windows where the extra call isn't required, and there's unfortunately no room to insert a note on this page. I'm posting it here as a clarification instead.

For more on this call, see its usage in the calendar program example I posted as a supplement for book readers at:


The call is demonstrated in this program's file, using this code:

if sys.platform.startswith('linux'):

2) Uniform gridding: tables (page 564)

The frigcal program and file also illustrates uniform-sized grids, similarly not shown in the book. It uses the following code (experiment on your own to see its effect):

for week in range(MAXWEEKS):
alldaysfrm.rowconfigure(week, weight=1, uniform='a')
for day in range(7):
alldaysfrm.columnconfigure(day, weight=1, uniform='a')

3) Unicode limitations

The Tk library underlying Python's tkinter has also been shown to have limits with respect to allowable Unicode characters. Odd emoticons in emails, for example, can wreak havoc with email clients like the book's PyMailGUI; code workarounds as warranted (e.g., sanitize text before storing in GUI text fields).

4) Widget.after() hangs

Finally, it's been recently discovered that the tkinter widget.after() timer event can be broken by changes to the system time. In short, the underlying Tk library computes an absolute time for running a scheduled after() event. If Windows automatically adjusts your clock, pending after() callbacks may appear to hang, because their absolute firing time is not reached. This can manifest in the book's PyClock clock and PyMailGUI email client examples, as both rely on timer events (to redraw clocks, and run queued thread actions). This is a Tk issue, and may be repaired in future Tk releases; for now, try changing your clock to unhang such programs.

Mark Lutz
Oct 18, 2014 
Page 452
Second to last paragraph

the last sentence of that paragraph:

The script also arranges for just the entry fields to grow vertically on a resize, as in Figure 8-24.

I think it should be horizontally, not vertical. as the fill option is X in the script.

Note from the Author or Editor:
REPRINTS: please change the referenced clause on page 452 from:
to grow vertically on a resize, as in Figure 8-24.
to the following:
to grow horizontally on a resize, as in Figure 8-24.

DISCUSSION: yes, and good catch again. This should be obvious to readers from the code; the code's "# grow horizontal" comment; and the screenshot that shows the expansion, but it's worth patching to be correct in this location.

Xiaohua Yuan  Nov 20, 2019  Feb 26, 2021
Page 493
first paragraph

The example assumes that images are stored in an images subdirectory, and it
allows the image filename to be passed in as a command-line argument (it defaults to spam.gif if no argument is passed).

the defaults should be london-2010.gif

Note from the Author or Editor:
REPRINTS: please change the page 493 clause from:
(it defaults to spam.gif if no argument is passed)
to the following, with filename font for the replacement):
(it defaults to london-2010.gif if no argument is passed)

DISCUSSION: yes, and good catch. The code that follows this in the book and its file in the examples package clearly default to ''london-2010.gif':

imgdir = 'images'
imgfile = 'london-2010.gif'
if len(sys.argv) > 1: # cmdline argument given?
imgfile = sys.argv[1]
imgpath = os.path.join(imgdir, imgfile)

This is also obvious from the screenshot that follows. Though the history on this is lost, it probably reflects a late change to the code, which overlooked the narrative's reference in the rush.

Xavi Yuan  Nov 28, 2019  Feb 26, 2021
Printed, PDF, ePub, Mobi, Safari Books Online
Page 631
Source code def configBorders

The method of setting a window icon as used in the generic _window class, does not work on linux. This seems to be part because the .ico file is only for windows and part because tk has a hard time decoding any image file. Following code seems to work:

root = Tk()
img = PhotoImage(file='your-icon')'wm', 'iconphoto', root._w, img)

Note from the Author or Editor:
[No Edits Required: informational only, changing this report's type from minor technical mistake to clarification.]

It's true that Windows ".ico" files won't generally work on Linux, but:

1) This is pretty clearly explained in a big paragraph on page 424, where tk's iconbitmap call is first covered in depth, and mentioned parenthetically on page 44 at a preview example (plus, the book makes its use of Windows for running examples explicit throughout).

2) The _window class on Page 631 referred to in this post should not cause its clients to fail on Linux, as it wraps the icon call in a exception handler and simply ignores the error on other platforms (to be verified, but it should only skip the custom window icon step and continue).

So, this isn't an errata per se; the example works as shown, and requires tweaking outside Windows for custom icons as described in the book. That's one of the unavoidable platform dependencies of GUIs, and it wasn't possible to cover every variant.

That said, I'm retaining this note here as a constructive and useful pointer for other Linux readers who may want to use a custom window icon too; thanks for the suggestion.

On Linux, you'll probably also want to customize the _window class's iconpatt string, used to search for an icon file automatically if none is passed in (see the code: it's "'*.ico" as is).

Rob van der Most  Oct 06, 2013 
Page 668
example source code

Not sure if I'm doing something wrong but there does not seem to be a PhotoImage module/class which the PyGadgets or PyDemo script tries to use. It tries to import it from Gui/PIL, but it does not exist anywhere in the PP4E tree. If I use the PhotoImage that exists in tkinter, a run-time error results when it references an open( no such method.
I am using Python 3.7.6 (v3.7.6:43364a7ae0, Dec 18 2019, 14:18:50)

Note from the Author or Editor:
[No changes required] Have you worked this out on your own yet? PhotoImage is a class in Python's standard tkinter module, and appears with documentation at numerous places in the GUI parts of the book (especially the introduction and tutorials).

As also covered in the text, the third-party PIL library (now called Pillow but still imported as PIL) has a PhotoImage class too, which replaces tkinter's, and adds more advanced image-type support and extra functionality leveraged by the book's examples. To use this, you must install Pillow separately from Python. See page 491 and later for PIL, especially; example:

% python3
>>> from tkinter import PhotoImage # the standard lib's module
>>> PhotoImage
<class 'tkinter.PhotoImage'>
>>> from PIL import ImageTk # the PIL/Pillow replacement
>>> ImageTk.PhotoImage
<class 'PIL.ImageTk.PhotoImage'>

One note: if you're on Linux, the incantation for installing PIllow is a bit obscure (and the install docs at seem out of date); the following suffices on Ubuntu today, and a web search for "ImageTk PiIlow linux" turns up additional hints:

sudo apt-get install python3-pil python3-pil.imagetk

One nit: the PIL project eventually became a fork called Pillow; the two are almost identical, but to use Pillow instead of PIL, you must change any appearances of the first of the following to the second:

import Image
from PIL import Image

I don't, however, see the first form used anywhere in the book's examples, so this isn't relevant to your post. I'd suggest ensuring that you've installed and are using Pillow's class, if your calls are not working. And if you're truly curious, you can find PhotoImage's source code in the package folder "tkinter/" of Python's standard libs install on your computer.

This post was retained in case other readers have similar queries, but its type was changed from "serious mistake" to "clarification." For more details, try these related book-support pages:

Doug Day  Oct 09, 2020 
, Printed, PDF, Safari Books Online, Other Digital Version
Page 678
line 3 of last paragraph on page

[Feb-1-11] Page 678 in Chapter 11, line 3 of last paragraph on page, figure description off

The text misstates Figure 11-4's content here: it does not show a Clone window (the original version of this screenshot did, but was retaken very late in the project to show Grep dialogs with different Unicode encodings). To fix, change this line's "a window and its clone" to read "a main window".

Mark Lutz
Feb 02, 2011  May 13, 2011
, Printed, PDF, Safari Books Online, Other Digital Version
Page 702 and 704
see detailed description

[For a detailed description of this item, as well as the
correct whitespace in the patch, please see:

[Feb-1-11] Page 702 and 704, PyEdit: add text.focus() calls after askstring() Unicode popups

For convenience, and per the detailed description above, we should add a call to reset focus back to the text widget after the Unicode encoding prompt popups which may be issued on Open and Save/SaveAs requests (depending on texconfig settings). As is, the code works, but requires the user to click in the text area if they wish to resume editing it immediately after the Unicode popup is dismissed; this standard popup itself should probably restore focus, but does not. To fix, add focus calls in two places. First, on page 702, at code line 21 at roughly mid page, change:

if askuser:
text = open(file, 'r', encoding=askuser).read()

to the following, adding the new first line (the rest of this code is unchanged):

self.text.focus() # else must click
if askuser:
text = open(file, 'r', encoding=askuser).read()

Second, on page 704, at code line 8 near top of page, similarly change:

if askuser:

to the following, again just adding the new first line:

self.text.focus() # else must click
if askuser:

Reprints: please let me know if there is not enough space for the inserts; I'd rather avoid altering page breaks in the process. This patch will also be applied to future versions of the book's examples package; in the package, the code in question is in file PP4E\Gui\TextEditor\, at lines 298 and 393.

Mark Lutz
Feb 02, 2011  May 13, 2011
Page 744
Middle page line 234 of source code

def traceEvent(label, event, fullTrace=True):
if fullTrace:
for atrr in dir(event):
if attr[:2] != '__':
print(attr, '=>', getattr(event, attr))

This line:
for atrr in dir(event)

Should be:
for attr in dir(event)

Note from the Author or Editor:
Yes, change "atrr" to "attr" in the code referenced. This is near the end of example 11-18, in function traceEvent, in line "for atrr in dir(event):". (This code is never used, due to the fullTrace parameter aways being False; still worth a fix.)

Michael Basca  Feb 18, 2015  Aug 07, 2015
Page 830
3rd line of code

In the comment header of the file, it should be:

"test the modes"

instead of:

"test the modes"

Note from the Author or Editor:
Yes - please change the indicated 1 line of code comment from:
"test the modes"
"test the modes"

Martin Alonso  Jul 08, 2017  Jan 26, 2018
Page 834
Above last paragraph

In line 27 of

# FAILS on open: text must be unbuffered

I believe should be:

# FAILS on open: text must be buffered


# FAILS on open: text must be fully buffered

Note from the Author or Editor:
Yes; this is in the last line of example 12-12. Change the comment text on the right to "# FAILS on open: text must be buffered" (that is, change just "unbuffered" => "buffered"). This is just a comment and is implied by the surrounding narrative that describes this correctly, but merits a fix.

Michael Basca  Feb 24, 2015  Aug 07, 2015
Page 839
1st sentence

The net effect is that -u still works around the steam buffering issue

should be:

The net effect is that -u still works around the stream buffering issue

Note from the Author or Editor:
Yes, change "steam" to "stream" as suggested.

Michael Basca  Feb 25, 2015  Aug 07, 2015
Page 896 & 899
in 896 line 7 in code of; in 899 2nd paragraph

In references to directory and file:


They do not exist.

Note from the Author or Editor:
[NO EDIT REQUIRED, retained as informational only]

No, Tools\Scripts\ does exist, at least as of Python 3.3, in the standard Windows install.

This post refers to a cryptic program comment and brief narrative mention that refer to Python's ftpmirror script as another example to study, as it's related to the FTP directory tree deletion script in the book.

This script has been standard on Windows installs for many years, but has moved in the past, and may be elsewhere on Macs or Linux. Run a find to locate if needed, and try a web search or Python's source-code package as a last resort. This isn't discussed further in the book than this brief comment mention, so no elaboration is required.

Michael Basca  Mar 03, 2015 
, Printed, PDF, Safari Books Online
Page 963, 970
Page 963 line 9, and page 970 line 4

Page 963 line 9, and page 970 line 4: add timeout arguments to email server connect calls

For robustness, add "timeout=15" arguments to the POP and SMTP connect calls, so that email clients don't hang when email servers fail to respond. In the book, change code line 9 on page 963 from the first of the following to the second:

server = smtplib.SMTP(self.smtpServerName) # this may fail too
server = smtplib.SMTP(self.smtpServerName, timeout=15) # this may fail too

Similarly, change code line 4 on page 970 from the first of the following to the second:

server = poplib.POP3(self.popServer)
server = poplib.POP3(self.popServer, timeout=15)

In the book examples package, these changes would be applied to line 153 of, and line 34 of file, both of which reside in directory PP4E\Internet\Email\mailtools. They'll be patched in a future examples package version.

Not a bug, but a desired enhancement. For more background on this, see my updates page:

Mark Lutz
Feb 22, 2011  May 13, 2011
Page 966
line 14 & 15 or 325 & 326 in code


class MailSenderAuth(MailSender):
use for servers that require login authorization;
client: choose MailSender or MailSenderAuth super
class based on mailconfig.smtpuser setting (None?)
smtpPassword = None # 4E: on class, not self, shared by poss N instances

def __init__(self, smtpserver=None, smtpuser=None):
MailSender.__init__(self, smtpserver)


def __init__(self, smtpserver=None, smtpuser=None):
MailSender.__init__(self, smtpserver)

Need to initialize with tracesize similar to MailSender class:

def __init__(self, smtpserver=None, smtpuser=None, tracesize=256):
MailSender.__init__(self, smtpserver, tracesize)

Or else you'll get error message not recognizing tracesize=5000 on line 24 of

Note from the Author or Editor:
Yes; this is a minor but tricky code change, so please ask if unclear. On page 966, change the class in question as described, adding just ", tracesize=256" to the "def __init__" header line, and " , tracesize" to the line following the header:

class MailSenderAuth(MailSender):
def __init__(self, smtpserver=None, smtpuser=None, tracesize=256):
MailSender.__init__(self, smtpserver, tracesize)

DISCUSSION: This was detected in test code that was never run for the book, because the test server didn't require authentication; the non-authenticated branch was always run instead. Also note that this is only used in a trivial self-test script; the authenticated sender class itself works fine, and has been used for years to communicate with an ISP that requires logins for sends in the book's PyMailGUI client. No other clients attempt to pass in a tracesize. We could just remove tracesize from, but that seems like cheating...

Michael Basca  Mar 14, 2015  Aug 07, 2015
, Printed, PDF, Safari Books Online, Other Digital Version
Page 978, 964
mid page (see description)

[Oct-20-11] Pages 978 and 964, encode and decode i18n attachment filenames for display, save, send

Per the detailed description at, the following two changes will support both receipt and send of encoded i18n attachment filenames, assuming that such non-ASCII filenames are valid on the underlying platform (Windows is very liberal in this regard).

First, on Page 978, change the very last line of the partName method def statement from the first of these to the second (this is mid page at code line 26, in file PP4E\Internet\Email\mailtools\ -- be careful of indentation of the code and its "#" comment which is given more exactly in the detailed description named above):

return (filename, contype)

return (self.decodeHeader(filename), contype) # oct 2011: decode i18n fnames

Second, on Page 964, change the 5th and 4th last lines of the addAttachments method def statement from the first of these to the second (this is mid page line -22, in file P4E\Internet\Email\mailtools\

# set filename and attach to container
basename = os.path.basename(filename)

# set filename (ascii or utf8/mime encoded) and attach to container
basename = self.encodeHeader(os.path.basename(filename)) # oct 2011

These were also patched in version 1.3 of the book examples package (; see for details.

Mark Lutz
Oct 20, 2011  Nov 11, 2011
, Printed, PDF, Safari Books Online, Other Digital Version
Page 1072
code line 10 from top of page

[For a detailed description of this item, as well as the
correct whitespace in the patch, please see:

[Feb-1-11] Page 1072, code line 10 from top of page, PyMailGUI: add a close() for HTML mail files

For portability, and per the detailed description above, we should add an explicit close() call to flush the temporary file of an HTML-only email before starting a web browser to view it, so that this code works in all contexts. As is, it works on the test platform used for the book, and likely works on most others, because the method in question exits and thus reclaims, closes, and flushes the file before the spawned web browser gets around to reading it. However, this is timing and platform dependent, and may fail on some machines that start browsers more quickly; its been seen to fail on a fast Vista machine. To fix in the book, change the middle line of the following three current code lines:

tmp = open(tempname, 'wb') # already encoded
webbrowser.open_new('file://' + tempname)

to read as follows, adding the text that starts with the semicolon (I'm combining statements to avoid altering page breaks):

tmp = open(tempname, 'wb') # already encoded
tmp.write(asbytes); tmp.close() # flush output now
webbrowser.open_new('file://' + tempname)

In the book's examples package, this code is located at line 209 in file PP4E\Internet\Email\PyMailGUI\; it will be patched there too in a future examples package release (version 1.2, date TBD).

Mark Lutz
Feb 02, 2011  May 13, 2011
Page 1098 of 41048
Last paragraph

I am also getting a raise of 10% for Doe. Looks like Manager glass's customization giveRaise doesn't work.

I tried it with my code and using downloaded examples from the book.

I am using latest Python 3.6.3 on conda 4.4.6

Note from the Author or Editor:
[Not errata, no changes required, retained as clarification only]

No, this code in the book and its examples package works as shown and described: the Manager instances gets an extra 10% bonus tacked on to the raise amount passed in. This is true for both the original and pair, as well as the recoding. Perhaps you've already realized this on your own after the post, but the results in all cases are fairly clear about the total raises applied for Managers:

>>> x = 50000
>>> x *= (1 + .10) # if just 10% was applied (non-Manager Persons)
>>> '%.2f' % x
>>> x = 50000
>>> x *= (1 + .10 + .10) # a 10% raise plus a 10% bonus (Managers)
>>> '%.2f' % x
>>> x = 50000
>>> x *= (1 + .20 + .10) # a 20% raise plus a 10% bonus (other examples)
>>> '%.2f' % x

If you're still not convinced, I encourage you to trace through the examples again and desk-check their results; the small details can be confusing on first glance.

Igor Bartolic  Jan 05, 2018 
Page 1219
Bottom of page in comments

caveat: could open output file in text mode to wite receiving platform's line

wite => write

Note from the Author or Editor:
Yes, in line 1 of paragraph 2 in code comments section, change the "mode to wite" to "mode to write". This is just cryptic and not grammatically-correct code comments text, but worth an edit.

Michael Basca  Apr 03, 2015  Aug 07, 2015
, Printed, PDF, Safari Books Online, Other Digital Version
Page 1226
sidebar on page

[Feb-1-11] Page 1226, two filename typos in same sidebar

This will probably be obvious to most readers who inspect the external example files referenced here, but in this sidebar: "test-cgiu-uploads-bug*" should read "test-cgi-uploads-bug*", and the bullet item text "test-cgi-uploads-bug.html/py saves the input stream" should read "test-cgi-uploads-bug2.html/py saves the input stream".

Mark Lutz
Feb 02, 2011  May 13, 2011
Page 1238
Bottom of the page last sentence

The following sentence:

The server’s classes’ implementation varies
over time, but if changes to your CGI scripts have no effect, your platform my fall into this category: try
stopping and restarting the locally running web server.

Needs the word 'my' to change to 'may'.

Note from the Author or Editor:
Yes, change the referenced "your platform my fall into" to "your platform may fall into".

Michael Basca  Apr 03, 2015  Aug 07, 2015
Page 1280
Bottom of the page

I think this is a typo?

from Crypto.Cipher import AES>>> AES.block_size16

When i plugged this into the interpreter python yielded a syntax error due to the '>>>'

I changed it just to:

from Crypto.Cipher import AES

and followed the steps to receive the same results. (cypher text is now bytes vs. string in Python 3.4)

Note from the Author or Editor:
Yes, change this line to the following, where its "#" aligns vertically with the "#" in the line following it:

>>> from Crypto.Cipher import AES # AES.block_size is 16

It appears that this reflects a conversion error from the 3rd edition's material. In the 3rd edition, this was the 3 lines that follow (with incorrect bold formatting on some), intended to show that block size is named in this library that's only thinly documented by the book:

>>> from Crypto.Cipher import AES
>>> AES.block_size

In the 4th edition, the conversion to MS-WORD for editing munged these 3 lines into 1, which eluded all later proofing. We don't appear to have space to restore the 3rd Ed's form, so making the text a comment will suffice.

There's nothing we can do in this edition about the change from str to bytes in PyCrypto, unfortunately; it's too much code to change, and would not apply to or work for users of earlier Pythons that this edition addresses. Such updates are in the realm of a future edition.

Michael Basca  Apr 07, 2015  Aug 07, 2015
Page 1308
Last line 2nd bullet.

Original line:

Pythons without any of these automatically fall back on an all-Python and always-present implementation called dbm.dumb, which is not really “dumb,” or course, but may not be as fast or robust as other options.

or course => of course

Note from the Author or Editor:
Yes; please change as described, to "of course" (of course...)

Michael Basca  Apr 08, 2015  Aug 07, 2015
Page 1357
Top of the page.

from PP4E.Dbase.testdata inport Actor

inport => import

Note from the Author or Editor:
Yes, change "inport" to "import" as described. In an unrun code snippet, not a formal example or examples file, but still worth a patch.

Michael Basca  Apr 14, 2015  Aug 07, 2015
Page 1412
2nd to last paragraph

Last sentence from 3rd paragraph:

Although such parsing can also be achieved with more
powerful tools, such as the regular expressions we’ll meet later in this chapter, split-based parsing is simper to code in quick prototypes, and may run faster.

simper --> simpler

Note from the Author or Editor:
Yes: change the referenced "simper" to "simpler".

Michael Basca  May 10, 2015  Aug 07, 2015
, Printed, PDF, Safari Books Online, Other Digital Version
Page 1555
heading line at top of page

[Feb-1-11] Page 1555, top of page, quotes are misplaced in heading line

A typo inherited from the prior edition: the quotes and question mark in the heading line at the very top of this page are slightly off. Change the heading line: So Whats Python: The Sequel? to read as: So Whats Python?: The Sequel.

This header refers back to the sidebar in the Preface titled "So What's Python?". Arguably trivial, as this sidebar was 1500 pages (and perhaps a few months) ago by this point in the book, but it would be better to get this right. This header was broken by a copyedit change on the prior edition, and fell through the cracks on this one.

Mark Lutz
Feb 02, 2011  May 13, 2011
Page 9900
1st paragraph

This is not an error but a suggestion for easing the problem of delayhed evaluation of a function when its arguments may have changed. At location 9916 on Kindle version.

But you must still sometimes use defaults instead of enclosing scopes

Lutz, Mark. Programming Python (Kindle Location 9916). O'Reilly Media. Kindle Edition.

You could Curry the function, then give the local arguments. It is a little different from the usual Curry because you don't want to evaluate the function after all arguments have been passed, but on the next call. That way the arguments are evaluated when added.

Tyhe example at location 9900 looks like:

command = Curry handler)(X, 'spam')

Note from the Author or Editor:
[No changes required - informational only] (This topic starts on Page 387 of the printed book, and is also covered in Learning Python's Chapter 17.)

Thanks for your note. Naturally, there's no room to expand on this topic in the book today, but I'm retaining this as a pointer here for other readers. The web has additional resources on currying that may be of interest.

In short, currying (and its cousin, partial application) are indeed ways to retain state for use in later calls, like the state needed in the referenced example -- the current values from an enclosing scope. Still, Python has additional schemes which some may consider more traditionally Pythonic. As discussed in the book, these include nested function closures, as well as instances of classes. The latter can make the state retention explicit (and arguably less "magic") with attribute values filled out in __init__, and either called or bound methods, or __call__ to implement a function-call interface. Because Python has such tools, currying is often a secondary option in its realm, where "flat is better than nested" is still considered a coding virtue.

While all such options are interesting from a programming languages perspective, they may also be a bit much in the referenced example. In its case, default argument values suffice to attach values to the lambda functions themselves, and serve well and simply as "memory" for later calls. Then again, there's more than one way to bind data to function calls, especially if you're coming from a background in other languages.

Clifford Ireland  Nov 03, 2018