Below are book clarifications applied in the first reprint
of this book, dated January 2010. Their suggested updates
are not present in paper copies of the book's first printing
(September 2009).
- [Oct-28-09] Preface, book URL: not an error
[No fix required] A reader wrote that "Web address http://www.oreilly.com/9780596158064 is
incorrect. It should be http://www.oreilly.com/9780596158071." Really,
neither of those URLs work, but http://www.oreilly.com/catalog/9780596158064
and http://www.oreilly.com/catalog/9780596158071 both do. Since the first
of these is the URL shown in the book, this is a non-issue (though I'm not
sure why the latter works too, as it doesn't reflect the stated ISBN number).
- [Oct-28-09] Page 86, mention XML parsing in types intro chapter 4
In paragraph 3 on this page, I want to mention XML parsing at the end of the
strings introduction. Change
"even more advanced language processing, including natural language
processing."
to read
"even more advanced text and language processing, including XML parsing and
natural language analysis." XML parsing is mentioned in the intro section
of the in-depth string chapter 7 along with a forward link to XML examples in
Chapter 36, so this is perhaps nitpicking; an XML teaser here is warranted,
though.
- [Nov-03-09] Page 90, generators and iteration protocol: iter() optional
The first code listing on this page shows a generator
expression, and a valid but intentionally abbreviated exploration of applying the
iteration protocol to it. As suggested briefly later in this chapter and explained
more completely later in the book, the full realization of the iteration protocol
here would be: G = (...); I = iter(G); and next(I) until StopIteration is raised.
Because generators are their own iterator (that is, iter(G) returns G itself), the
code on this page is able to skip the iter(G) call and proceed to next(G).
This is all explained in-depth later in the book, and this chapter is explicitly
designed to be an incomplete first look (like much in this chapter, the full story
on iteration is far too subtle for a first pass over built-in types).
Still, this example might seem to contradict the iterator protocol description later
in this chapter on first glance. To avoid some confusion, on the second line of page
90 (the code line that reads ">>> next(G)"), add a comment near the end of this line
that reads "# iter(G) not required here", where the "#" in this lines up
vertically with the "#" of the preceding line. That is, the first two lines on this
page should look like:
>>> G = (sum(row) for row in M) # Create a generator of row sums
>>> next(G) # iter(G) not required here
- [Nov-08-09] Page 91, mention the dict(name=val) form in intro chapter
On page 91, at the very end of the last paragraph before heading "Nesting Revisited",
add the following sentence: "As we'll learn later, dictionaries may also be made
by passing keyword arguments to the type name: dict(name='Bob', job='dev', age=40)
makes the same dictionary.", where the "dict(...)" part in this is code in
fixed-width font. [Reprints: it looks like there is space for the insert, but
please ask me how to shorten if not.]
A related page 186 insert is described ahead.
I'm adding this because the dict() call isn't formally described until the
list/dict chapter on pages 208 and 217, but pops up in a few examples shortly before
that--in some new str.format() calls on pages 186-189. In fact, I would mark this
unnoted forward reference as an error (other forward references usually say they are),
except that its usage is probably apparent to most readers; it does get covered just
20 pages later; and because of the general book-wide principles of the following
paragraph.
This, and keyword arguments in general, represent broader circular or forward
dependency issues in Python today--you have to fully understand functions and
advanced call syntax before you fully get str.format(), list.sort(), dict(name=val),
and print(). This is especially true in 3.X; the need to wrap 3.X iterables in
list() to see their values, for instance, is another such conceptual hurdle that
confronts beginners very early on. The net effect is that in a linear and bottom-up
book like this, you need to take some things on faith until they are formally
presented in their entirety; occasionally skip ahead using the Index and Table of
Contents; or consult a reference in parallel. For instance, both the Python Library
Reference and the book Python Pocket Reference describe dict(), and other
built-ins that show up in examples, in more detail than this book intends to. As
stated at the start of the Preface, this book is a gradual tutorial by design,
not a reference, and is intended to be used in conjunction with a reference. When
in doubt, either stay tuned or search for earlier elaboration. (See also the
elaboration of this subject in the general book notes
elsewhere on this page.)
- [Nov-03-09] Page 98, binary file intro example: assumes a file exists
The last code listing on this page opens and reads a file named 'data.bin'. For the
purposes of this introductory overview chapter on types, it assumes this file exists,
but does not explicitly create it to avoid getting too deep into the 3.0 bytes topic
(i.e., it's a deliberately partial example like much in this chapter). If you are
working along with the examples here, though, note that this code (and its
elaboration on page 234) won't work unless you've already created the binary data
file in your working directory; to see how to do so, watch for the more in-depth
files coverage of Chapter 9 and 36. To minimize confusion, though, we'll add the
following text at the very end of the last paragraph on page 98, just before the
colon there:
"(the following partial example assumes there is already a binary file in your
current directory)". [Reprints: omit "partial example" if this doesn't fit.]
- [Nov-03-09] Page 123, three numeric tests: run under 2.X not 3.X
I ran the last code listing on page 123 (along with its continuation on the next
page) under Python 2.6 on purpose, in order to show the long precision used for
large integers. This should be obvious given the discussion of the "L" suffix for
2.6 longs earlier in the chapter. I didn't explicitly state that this example gives
2.6 output format, though, and that may confuse, given that other examples are either
3.X or labeled as 2.X. To clarify, at the end of the last paragraph on page 123 and
just before that page's last code listing, add "(run in 2.6 here to reveal the
long precision)" just before the colon there. For reference, here's how this
looks if run in 3.0 -- it uses long precision, but you can't really tell:
>>> X = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF
>>> X
5192296858534827628530496329220095
>>> oct(X)
'0o17777777777777777777777777777777777777'
>>> bin(X)
'0b11111111111111111111111111111111111111111111 ...and so on...
- [Oct-28-09] Page 135, set items are immutable (though sets are not)
At the start of the 2nd paragraph under "Set literals in Python 3.0", the book
states that "This syntax makes sense, given that sets are essentially like
valueless dictionaries--because they are unordered, unique, and immutable, a
set’s items behave much like a dictionary’s keys." One reader suggested
changing "immutable" to "mutable" here, but that would break the intent.
This description is technically correct as is, since its last half is discussing
the items in a set rather than a set itself. Still, it's a bit ambiguous as worded.
For clarity, change the second part of this sentence as follows: "This syntax
makes sense, given that sets are essentially like valueless dictionaries--because
a set's items are unordered, unique, and immutable, the items behave much like a
dictionary’s keys."
- [Nov-09-09] Page 185, *args in call forward reference (but noted earlier)
On this page, the syntax '...'.format(*parts) makes use of special call
syntax to unpack an iterable's items into separate arguments. This feature isn't
covered in full until much later in the book. This isn't a forward reference error,
though -- although it's not noted here specifically, this syntax is noted
briefly earlier in the book. For forward references like this, the book doesn't
necessarily repeat the note at every occurrence. To clarify further, though, add the
following at the very end of the second paragraph on page 185 and just before the
colon there: "(note the use of *parts here to unpack a tuple's items into
individual arguments, as we did on Page 132 when studying fractions; more on this
in Chapter 18)". The "*parts" in this should be fixed-width font.
Specifically, this call syntax is described in conjunction with fraction ratios on
page 132, which states: "(the * in the second test is special syntax that expands a
tuple into individual arguments; more on this when we study function argument passing
in Chapter 18)". A **dict forward reference is also noted on page 189, which states:
"(as we’ll see in Chapter 18, the **data in the method call here is special syntax
that unpacks a dictionary of keys and values...)". Like many things in this book,
full coverage is deferred until later in the book.
Frankly, the str.format() section here was pulled from an introduction I wrote
independent of this book; that's why dict() also appears here before its coverage
(see the page 91 and 186 clarifications). However, while the "*args" might confuse
here (especially since "*" is also used to pull width/precision from arguments in "%"
expressions), it's impossible to show complete str.format() utility without a few
forward references (its usage can depend upon keyword arguments, *args, dictionaries,
and so on). In this case the tool used was noted earlier, but this is really an
instance of the general forward dependency issue of Python's current design described
further in the general notes section elsewhere
on this page.
- [Nov-09-09] Page 186, dict() forward reference (to refer back to page 91 insert)
This is related to the clarification for page 91 earlier on this page. On page 186,
at the very end of the 3rd-last paragraph that ends with "10-character-wide field",
add this text just before the colon there: "(note the use of dict() to make a
dictionary from keyword arguments, introduced in Chapter 4 and covered in Chapter 8)".
The "dict()" in this should be fixed-width font. It's impossible to add forward notes for every
deferred topic like this, but this case seems especially glaring to me.
- [Nov-10-09] Page 195, bytearray is mutable too (type summary is informal)
I resisted including both 3.X's bytes and bytearay types and 2.6's unicode type in the
datatype lists in this section on purpose, to avoid getting too fine grained or version
specific at this point in the book. bytes, bytearray, and 2.6 unicode are kinds of
strings; are introduced earlier in this chapter but mostly deferred till Chapter 36;
and are included in the later type summaries and figures in Chapter 9 (see pages 240
and 249). Since bytearray is available in both 2.6 and 3.X, though, we'll clarify this
a bit here: at the end of the very first line on page 195, change
"Mutables (lists, dictionaries, sets)" to
"Mutables (lists, dictionaries, sets, bytearray)".
- [Nov-08-09] Page 217, keywords appear in other places too (links, dependencies)
[No fix required] When presenting the dict(name=value) form on this page,
it's mentioned that keyword arguments were encountered earlier at list.sort().
True, but they also appear at str.format() on page 184 and beyond, where it is
noted that they are covered in full in Chapter 18, as well as in print() calls
on pages 164 and 200, where they are not explained at all. I'm not going to
patch this page because page 184 links to the full coverage, and this is a general
forward dependency issue that is further described at the page 91 clarification
above and in the general book notes pages--in a linear book like
this, such dependencies have to be deferred or presented in piecemeal fashion.
Again, you can always look up the fine points of dict(), print(), and others in
references; watch for the in-depth coverage of keyword arguments in Chapter 18;
or jump to this coverage early--much of it is accessible through this book's Index
and Table of Contents.
- [Nov-01-09] Page 413, Chapter 17, scopes and loop variables in comprehensions (add new footnote)
I wish to clarify an arguably obscure scope issue not covered in the book --
that of loop variables in comprehensions. In 2.X, such loop variables are
local to generator expression, but not list comprehensions. In 3.X, they
are always local to both kids of expressions, as well as 3.X's new set and
dictionary comprehensions. This differs from variables in loop statements
which are never local to the statement itself. It also seems almost too
academic and arcane to mention (and frankly, it's difficult to imagine a valid
use case that would depend upon either behavior), but this is technically
another 3.X change that could break 2.X code. Here's how this pans out in
practice -- in 3.X comprehensions localize loop variables, but statements don't:
# Python 3.0
>>> L = [x for x in 'spam']
>>> L
['s', 'p', 'a', 'm']
>>> x
NameError: name 'x' is not defined
>>>
>>> G = (y for y in 'spam')
>>> G
<generator object at 0x02501D00>
>>> y
NameError: name 'y' is not defined
>>>
>>> {x for x in 'spam'}
{'a', 'p', 's', 'm'}
>>> x
NameError: name 'x' is not defined
>>>
>>> {x: x*2 for x in 'spam'}
{'a': 'aa', 'p': 'pp', 's': 'ss', 'm': 'mm'}
>>> x
NameError: name 'x' is not defined
>>>
>>> for z in 'spam': print(z, end=' ')
...
s p a m >>> z
'm'
In Python 2.6, list comprehensions and generator expressions
are supported, but the former does not localize its loop variables -- they
retain their last iteration value, just like for-loop statements:
# Python 2.6
>>> L = [x for x in 'spam']
>>> x <=== DIFFERS
'm'
>>>
>>> G = (y for y in 'spam')
>>> y
NameError: name 'y' is not defined
>>>
>>> for z in 'spam': print z,
...
s p a m
>>> z
'm'
To clarify, in future printings of the book we'll add the following text as a
footnote at the bottom of page 413, with its star at the very end of the sentence
just before the note box on that page [reprints: please ask me how to shorten this
if it does not fit on that page as is]:
"""There is technically one more scope in Python: loop variables in
comprehension and generator expressions are local to the expression
itself in 3.X (in 2.X, they are local in generators but not in list comprehensions).
This is a special and obscure case that rarely impacts real code, and differs from
for-loop statements which never localize their variables.""".
- [Nov-06-09] Page 431, alternative scope-based state retention technique (add new footnote)
I want to point out an alternative to a book scopes example. The last section
on page 431 gives an example of using function attributes to retain per-call
state information, as an alternative to the 3.X-only "nonlocal" statement.
The following example from the book (which runs in both 2.X and 3.X) works
because each call to the outer function runs a nested def to make a new function
object; it also allows the state information to be accessed from outside the function
if needed:
# Python 2.X and 3.X: function attributes (in book)
>>> def tester(start):
... def nested(label):
... print(label, nested.state)
... nested.state += 1 # changes object, not name
... nested.state = start # per-call state is retained:
... return nested # each tester() makes a new func
...
>>> F = tester(0)
>>> F('spam')
spam 0
>>> F('eggs')
eggs 1
>>>
>>> G = tester(42) # each nested func object has its own state
>>> G('ham')
ham 42
>>> G('bacon')
bacon 43
>>>
>>> F('sausage')
sausage 2
>>> F.state, G.state # and state is accessible from outside
(3, 44)
Interestingly, you can achieve the same effect with a reference to
a mutable object in the enclosing scope instead of an attribute
attached to the function itself, albeit at some arguable cost in code
simplicity. The following alternative, not listed in the book, works
similarly because each call to the outer function gets a new local scope;
unlike function attributes, though, the state is not directly visible
outside the function:
# Python 2.X and 3.X: change mutable in enclosing scope (not in book)
>>> def tester(start):
... def nested(label):
... print(label, state[0])
... state[0] += 1 # changes object, not name
... state = [start]
... return nested
...
>>> F = tester(0) # per-call state retained again:
>>> F('spam') # each tester() has new local scope
('spam', 0)
>>> F('eggs') # but no F.state accessible here
('eggs', 1)
>>>
>>> G = tester(42)
>>> G('ham')
('ham', 42)
>>> G('bacon')
('bacon', 43)
>>>
>>> F('sausage')
('sausage', 2)
Of course, as shown in the book, the 3.X "nonlocal" statement offers an
alternative that is often seen as more straightforward than both the
above schemes; its only potential downside is that unlike function
attributes, the state it retains is not directly visible outside the function
(which may matter in some contexts):
# Python 3.X only: nonlocal statement (in book)
>>> def tester(start):
... state = start
... def nested(label):
... nonlocal state # change enclosing scope name
... print(label, state) # but state not visible outside
... state += 1
... return nested
...
>>> F = tester(0)
>>> F('spam')
spam 0
>>> G = tester(42)
>>> G('eggs')
eggs 42
>>> F('eggs')
eggs 1
>>> F.state
AttributeError: 'function' object has no attribute 'state'
Also see the further coverage of function attributes in page 471; this probably
should be noted at the earlier page 431 example, along with the fact that both
2.6 and 3.X support function attributes, and the rehash of these techniques in
the decorators chapter. To clarify all this a bit in future printings, make
two changes:
-
Add a footnote star at the very end of the first paragraph on page 432,
referencing a new footnote on page 432 that has the following text:
"""Function attributes are supported in both Python 2.6 and 3.X. We'll
explore them further in Chapter 19, and revisit all the state options
introduced here in Chapter 38 in a more realistic context. Also note that
it's possible to change a mutable object in the enclosing scope in 2.X and 3.X
without declaring its name nonlocal (e.g, state=[start] in tester, state[0]+=1
in nested), though it's perhaps more obscure than either function attributes or
3.X's nonlocal.""". Within this, "state=[start]", "tester", "state[0]+=1",
and "nested" should all be in fixed-width font.
-
Add page 471 to the index entry for "function attributes" (though it's
already mentioned in function/attributes and annotations).
[Reprints: please let me know if these two changes need clarification or trimming.]
- [Dec-15-09] Page 511, note decorator-based timer alternatives in ch38 and ch39 (add new footnote)
In Chapter 20, the book explores general timing functions whih require special
call patterns for the functions timed. Later in the book, decorator-based timer
alternatives are developed which don't require special call syntax, and it
would be nice to have a forward link to the alternative coding here.
Add the following text as new footnote on page 511, with its asterisk at the very
end of the paragraph just before heading "Timing Results" [reprints: it looks like
we have space, but please ask how to shorten if not]:
"""
Also note how we must pass functions in to the timer manually here. In Chapters 38
and 39 we'll see decorator-based timer alternatives with which timed functions are
called normally.
"""
- [Dec-14-09] Page 513-518, iteration timings, map() is slow because all function calls are slow (add new footnote)
This section goes through a code timing example and gives the relative
speeds of various iteration alternatives. The map built-in appears to
win when a built-in function is applied, and lose to list comprehensions
otherwise. All true, but this section wasn't meant as a complete treatise
on Python performance, and some additional conclusions were not explored
in the interest of space. Among these is a more general further conclusion
about map speed which I'd like to note here, and briefly footnote in the book.
Technically, map is relatively slower because it requires function
calls, and function calls are expensive in general. It doesn't matter
if the function called is built-in or user-defined. To see this for
yourself, try changing the timing examples to apply a simple user-defined
function; the results are the same as using a built-in function like abs --
map is faster if all five techniques call functions of any variety.
Unlike list comprehensions and for loops, though, map can't avoid the
cost of function calls, so it is slowed by association.
At least that's the case today. As also noted in the book, timing
results can vary per release and other variables, so be sure to time
code yourself to be sure. Performance measurement is often part
black art.
To expand on this in the book, add the following text as a new footnote
on page 518 (reprints: it looks like we have space on this page but please
ask how to shorten if not). Its asterisk should appear at the very end of the
last paragraph before heading "Function Gotchas" on page 518, and in this the
words "map" and "abs" and the code "def f(I): return(I)" should all be
fixed-with font:
"""
For more fun, apply a simple user-defined function like def f(I): return(I)
in all five iteration techniques timed. The results are similar to using a
built-in function like abs: among the five iteration techniques, map is fastest
today if all five call a function, built-in or not, but slowest when the others
do not. That is, map appears to be slower simply because it requires function
calls, and function calls are relatively slow in general. Since map can't avoid
calling functions, it can lose by association.
"""
- [Nov-11-09] Page 670, shelve changes and the "writeback" option
[No fix required] I want to make a minor clarification about shelves. Page
670 states that you must close shelves after making changes, and page 676 states that
ZODB writes changes to disk automatically, implying that shelve does not (also
implied by a code comment in the update script on page 674). This is all true by
default, and no fix is required here -- this book doesn't cover such libraries in
any sort of depth.
However, if the shelve.open() call's optional new writeback keyword argument
is passed True, all entries accessed are cached in memory, and written back at close
time. This makes it easier to change mutable entries in the shelve, but can consume
memory for the cache, and can make the close operation slow because all accessed
entries are written back. You still must close the file after changes as stated;
the only difference is that with this option you need not manually reassign changed
objects to their keys in the shelve in order to write changes to disk. See Python's
library manual, reference books, or application-focused books for more details on
shelve.
- [Nov-11-09] Page 746, use list() around dict.keys() in 3.X (a missed occurrence)
In the third-last line of the last code listing on this page, change ">>> x.keys()" to
">>> list(x.keys())". Without the list(), the output of this line shown is produced
in Python 2.X, not 3.X. I'm not marking this as an error since the need to wrap key lists in
list() for 3.X only is documented in numerous places before this; using list() is superfluous
for 2.X readers; and this book covers both 3.X and 2.X. Moreover, the rehash of this
example later on page 1013 does use the list() call. Still, list() is used
for key lists everywhere else to emphasize 3.X usage, so this occurrence might
confuse if not changed.
- [Nov-11-09] Page 728, Bound methods already defer calls -- avoid superfluous lambdas!
Insert a sentence at the start of the 5th paragraph on page 728, so that it reads:
"Note that a lambda is not required here, because a bound method reference
by itself already defers a call till later (again, more on bound methods in
Chapter 30). This technique is simpler, ...". It looks like there is plenty
of space on this page.
I'm adding this to underscore a point about deferred code which is already covered by
the book but is an apparently common stumbling block. This section, as well as the
more complete coverage in Chapter 30 with its sidebar on page 756, show how
bound methods, named through self, may be used as callback handlers, because they
are callable objects. For instance, the following registers self.handler as
the event handler for a tkinter GUI event. As explained in the book, referencing
the method without calling it like this (i.e., without coding parenthesis after it)
is sufficient to defer its call until the event later occurs and invokes a call:
>>> from tkinter import *
>>>
>>> class MyGui:
... def __init__(self):
... self.color = 'Blue'
... Button(text='Spam', command=self.handler).pack(side=TOP)
... def handler(self):
... print('Got it:', self.color)
...
>>> X = MyGui()
>>> mainloop() # and press button in GUI
Got it: Blue
Got it: Blue
Recently, though, I've seen the following sort of variation in a variety of
Python code on the web -- using a lambda to defer a method call. This shows up
even in tutorials for popular Python frameworks. But the lambda in this is
completely pointless -- if you simply leave the parenthesis off,
self.handler calls are already deferred:
>>> class MyGui:
... def __init__(self):
... self.color = 'Blue'
... Button(text='Spam', command=(lambda: self.handler())).pack(side=TOP)
... def handler(self):
... print('Got it:', self.color)
...
>>> X = MyGui()
>>> mainloop()
Got it: Blue
Got it: Blue
Both versions work, but the lambda in the second is superfluous overkill, and
probably reflects a lack of knowledge about Python's bound methods (if not bad
habits from other programming languages). Lambda is required in other contexts
that need more than a deferred method call (e.g., when you must also specify
arguments to be passed to the method); here, though, it's just extra code, and
incurs the performance penalty of an extra call.
This is exactly the sort of subtle mistake that abbreviated Python introductions
often miss, and which in-depth books like Learning Python are designed to
address and prevent. See, for instance, pages 727-728, 750-756, and 479. The
3rd Edition of the application-focused book Programming Python naturally
covers GUI callback coding options as well (especially on pages 390-399). You're
welcome to learn Python however you like, of course, but if you're going to skip
the full story altogether, you probably shouldn't expect your coworkers to maintain
your code!
- [Oct-28-09] Page 777, 2.6 classes must derive from object _explicitly_
In the second bullet on this page, I'd like to underscore that the derivation from
"object" in Python 2.6 new-style classes must be explicit. Change
"In Python 2.6 and earlier, classes must inherit from object" to
"In Python 2.6 and earlier, classes must explicitly inherit from object".
This is implied by the following code example, but the "explicit"
should be made (well...) explicit, since the preceding bullet does so.
- [Nov-12-09] Page 910, printing 2.6 unicode, display of non-ASCII str can vary
At the very end of the first paragraph on this page, change:
"(all other sections in this chapter are run under 3.0):" to
"; unicode characters display in hex in 2.6 unless you explicitly print,
and non-ASCII displays can vary per shell (most of this section ran in IDLE):".
[Reprints: please ask me to shorten if this doesn't fit as is.]
I'm changing this because: (1) The original isn't quite correct -- there are
other 2.6 sessions in this chapter; (2) The examples imply that echoes (repr)
display hex for 2.6 Unicode, but "print" displays characters -- this isn't
stated explicitly, though; and (3) The display of the first test in this
section varies between IDLE and a DOS shell interface on Windows. I ran this
in IDLE and got the results shown in the text, but a non-IDLE user might be
confused. Moreover, a later example gives the DOS display format, and this
all differs in 3.X, where all strings are Unicode and always display characters:
# Python 2.6, in the IDLE GUI (same as shown in book)
>>> S = 'A\xC4B\xE8C'
>>> S
'A\xc4B\xe8C'
>>> print S
AÄBèC
>>>
>>> U = u'A\xC4B\xE8C'
>>> U
u'A\xc4B\xe8C'
>>> print U <=== print in 2.X to show chars
AÄBèC
# Python 2.6, in a Windows Command Prompt (DOS shell)
>>> S = 'A\xC4B\xE8C'
>>> S
'A\xc4B\xe8C'
>>> print S
A─BΦC <=== DIFFERS from IDLE display
>>>
>>> U = u'A\xC4B\xE8C'
>>> U
u'A\xc4B\xe8C'
>>> print U
AÄBèC
# Python 3.1, in both interfaces (IDLE, DOS shell)
>>> S = 'A\xC4B\xE8C'
>>> S <=== no need to print() in 3.X
'AÄBèC'
>>> print(S)
AÄBèC
>>>
>>> B = b'A\xC4B\xE8C'
>>> B
b'A\xc4B\xe8C'
>>> print(B)
b'A\xc4B\xe8C'
- [Dec-14-09] Pages 941 and 983, managed attributes and decorators: accessors and aspects
[No fix required]
I've begun teaching some of the advanced topic chapter's subjects to students with
backgrounds in other languages, and they've drawn some analogies which readers may
find useful:
- For one, a prime use case for managed attributes is adding accessor functions
after the fact -- code that controls attribute access that isn't required in Python,
but often recommended. I'm not going to change the book for this, because this role is
strongly implied by the introduction to Chapter 37 (if not mentioned by name there),
and the notion of formal "accessors" is more important in other languages.
- For another, Python's function decorators map closely to what some other language
communities call aspects -- code run before or after a function call. I'm not
going to patch the book for this either, because the text does mention aspect-oriented
programming in relation to metaclasses in Chapter 39 (tough not for decorators in
Chapter 38), and as a rule tries to avoid descending into alternative programming
patterns promoted by other languages.
If you think in terms of "accessors" and "aspects", Python provides the support you
are accustomed to, though in Python concepts in other languages are often just
specific use cases of more general tools.
- [Dec-8-09] Page 1119, generator-based alternative to file counts exercise solution
If we have space, add the following sentence to the start of the last paragraph on page 1119:
"A generator expression can have much the same effect:
sum(len(line) for line in open(name)).", where the second half of this, "sum(...)",
is code in fixed-width font. I'd like to add this because this alternative generator
solution is as nice as the one shown, though arguably less explicit:
# shown: loads into memory all at once, fails for large files
>>> len(open('README.txt').read())
7013
# shown: reads one line at a time with file iterators
>>> tot = 0
>>> for line in open('README.txt'): tot += len(line)
>>> tot
7013
# not shown: generator expression -- reads one line at a time, sums along the way
>>> sum(len(line) for line in open('README.txt'))
7013
# other part: using binary mode to match system size (keep Windows \r\n)
>>> sum(len(line) for line in open('README.txt', 'rb'))
7216
>>> import os
>>> os.path.getsize('README.txt')
7216
See Chapter 20 for more on generator expressions, and Chapters 4, 5, and 14 for sum().
sum() allows any iterable, and both the file iterator and generator expression in this
alternative solution defer file line fetches until requested. The effect is to avoid
an explicit for loop, and perhaps speed performance.
- [Nov-07-09] Page 1141, Index, need entry for bitwise operations
The index really should include more about bitwise processing of integers;
as is, there are links to page 108 for operator symbols only, but this is
incomplete. Add an entry to the index, "bitwise operators (binary data)",
with links to pages "108, 124-125, 930-932". If this won't fit on the page,
shorten the title to "bitwise (binary) data" and/or make the links "108, 124, 932".