Learning Python 4th Edition: First Printing Clarifications

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).



  1. [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).



  2. [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.



  3. [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
    


  4. [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.)



  5. [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.]



  6. [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...
    


  7. [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."



  8. [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.



  9. [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.



  10. [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)".



  11. [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.



  12. [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.""".



  13. [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:

    1. 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.

    2. 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.]



  14. [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. """



  15. [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. """



  16. [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.



  17. [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.



  18. [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!



  19. [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.



  20. [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'
    


  21. [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:

    1. 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.

    2. 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.



  22. [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.



  23. [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".


Newer clarifications: see the recent clarifications page

Back to this book's main updates page



[Home page] Books Code Blog Python Author Train Find ©M.Lutz